From 583155c755c1016bb4c02b6e71dd4eec171d001a Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 14:18:20 +0300 Subject: [PATCH 01/15] 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 02/15] 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 03/15] =?UTF-8?q?improved=20performance=20by=20caching=20r?= =?UTF-8?q?egular=20expressions=20(roughly=2010=D1=85=20run=20time=20decre?= =?UTF-8?q?ase=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 From f4ff21fe2e085ca1310b28bacfd8961e93c1ca08 Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 17:03:40 +0300 Subject: [PATCH 04/15] temp commit --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 10 +++++++++- EDMXTrimmer/EDMXTrimmer/Program.cs | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index c49ea34..f342bfe 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -17,6 +17,9 @@ class EdmxTrimmer public bool RemoveActionImportsFlag { get; private set; } public string OutputFileName { get; set; } + public IReadOnlyCollection? ComplexTypesToKeep { get; private set; } + public IReadOnlyCollection? ComplexTypesToExclude { get; private set; } + private XmlDocument _xmlDocument; private XmlNode _firstSchemaNode; private string ENTITYNAMESPACE; @@ -49,7 +52,9 @@ public EdmxTrimmer( List entitiesToExclude = null, bool entitiesAreRegularExpressions = false, bool removePrimaryAnnotations = false, - bool removeActionImports = false) + bool removeActionImports = false, + IReadOnlyCollection complexTypesToKeep = null, + IReadOnlyCollection complexTypesToExclude = null) { this.EdmxFile = edmxFile; this.Verbose = verbose; @@ -71,6 +76,9 @@ public EdmxTrimmer( this.RemovePrimaryAnnotationsFlag = removePrimaryAnnotations; this.RemoveActionImportsFlag = removeActionImports; + ComplexTypesToKeep = complexTypesToKeep; + ComplexTypesToExclude = complexTypesToExclude; + this.LoadFile(); } diff --git a/EDMXTrimmer/EDMXTrimmer/Program.cs b/EDMXTrimmer/EDMXTrimmer/Program.cs index c677489..6b1aed2 100644 --- a/EDMXTrimmer/EDMXTrimmer/Program.cs +++ b/EDMXTrimmer/EDMXTrimmer/Program.cs @@ -7,18 +7,23 @@ namespace EDMXTrimmer { public class Options { + private const string EntitiesToKeepName = "entitiestokeep"; + private const string EntitiesToExcludeName = "entitiestoexclude"; + [Option( Required = true, HelpText = "EDMX source file")] public string EdmxFile { get; set; } [Option( + longName: EntitiesToKeepName, Required = false, HelpText = "Enter the public name & collection name. All values to be separated with commas. Supports ? and * wildcards.", Separator = ',')] public IEnumerable EntitiesToKeep { get; set; } [Option( + longName: EntitiesToExcludeName, Required = false, HelpText = "Enter the public name & collection name. All values to be separated with commas. Supports ? and * wildcards.", Separator = ',')] @@ -54,6 +59,17 @@ public class Options Default = false)] public bool RemoveActionImports { get; set; } + [Option( + Required = false, + HelpText = $"Enter action names to keep, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", + Separator = ',')] + public IReadOnlyCollection ComplexTypesToKeep { get; set; } + + [Option( + Required = false, + HelpText = $"Enter action names to exclude, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", + Separator = ',')] + public IReadOnlyCollection ComplexTypesToExclude { get; set; } } class Program { @@ -72,7 +88,9 @@ static void Main(string[] args) entitiesToExclude:opt.EntitiesToExclude.ToList(), entitiesAreRegularExpressions:opt.EntitiesAreRegularExpressions, removePrimaryAnnotations:opt.RemovePrimaryAnnotations, - removeActionImports:opt.RemoveActionImports); + removeActionImports:opt.RemoveActionImports, + complexTypesToKeep: opt.ComplexTypesToKeep, + complexTypesToExclude: opt.ComplexTypesToExclude); trimmer.AnalyzeFile(); } From d52f044ed201c4db80f6f5b4db35ff43a30de21e Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 17:13:35 +0300 Subject: [PATCH 05/15] added --RemoveFunctionImports option --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 17 ++++++++++++++++- EDMXTrimmer/EDMXTrimmer/Program.cs | 9 ++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index c49ea34..20ce007 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -15,6 +15,7 @@ class EdmxTrimmer public bool EntitiesAreRegularExpressions { get; private set; } public bool RemovePrimaryAnnotationsFlag { get; private set; } public bool RemoveActionImportsFlag { get; private set; } + private bool RemoveFunctionImportsFlag { get; } public string OutputFileName { get; set; } private XmlDocument _xmlDocument; @@ -32,6 +33,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 TAG_FUNCTION_IMPORT = "FunctionImport"; private const string ATTRIBUTE_ALIAS = "Alias"; private const string ATTRIBUTE_NAMESPACE = "Namespace"; private const string ATTRIBUTE_NAME = "Name"; @@ -49,7 +51,8 @@ public EdmxTrimmer( List entitiesToExclude = null, bool entitiesAreRegularExpressions = false, bool removePrimaryAnnotations = false, - bool removeActionImports = false) + bool removeActionImports = false, + bool removeFunctionImports = false) { this.EdmxFile = edmxFile; this.Verbose = verbose; @@ -70,6 +73,7 @@ public EdmxTrimmer( this.EntitiesAreRegularExpressions = entitiesAreRegularExpressions; this.RemovePrimaryAnnotationsFlag = removePrimaryAnnotations; this.RemoveActionImportsFlag = removeActionImports; + RemoveFunctionImportsFlag = removeFunctionImports; this.LoadFile(); } @@ -119,6 +123,11 @@ public void AnalyzeFile() RemoveActionImports(); } + if (this.RemoveFunctionImportsFlag) + { + RemoveFunctionImports(); + } + this._xmlDocument.Save(OutputFileName); Console.WriteLine($"Trimmed EDMX saved to file: {OutputFileName}"); if (Verbose) @@ -306,6 +315,12 @@ private void RemoveActionImports() .ToList() .ForEach(n => n.ParentNode.RemoveChild(n)); } + private void RemoveFunctionImports() + { + this._xmlDocument.GetElementsByTagName(TAG_FUNCTION_IMPORT).Cast() + .ToList() + .ForEach(n => n.ParentNode.RemoveChild(n)); + } private bool EntityExists(XmlNode xmlNode, string entityType) { var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; diff --git a/EDMXTrimmer/EDMXTrimmer/Program.cs b/EDMXTrimmer/EDMXTrimmer/Program.cs index c677489..4bc7976 100644 --- a/EDMXTrimmer/EDMXTrimmer/Program.cs +++ b/EDMXTrimmer/EDMXTrimmer/Program.cs @@ -54,6 +54,12 @@ public class Options Default = false)] public bool RemoveActionImports { get; set; } + [Option( + Required = false, + HelpText = "Function imports are removed from the EDMX file", + Default = false)] + public bool RemoveFunctionImports { get; set; } + } class Program { @@ -72,7 +78,8 @@ static void Main(string[] args) entitiesToExclude:opt.EntitiesToExclude.ToList(), entitiesAreRegularExpressions:opt.EntitiesAreRegularExpressions, removePrimaryAnnotations:opt.RemovePrimaryAnnotations, - removeActionImports:opt.RemoveActionImports); + removeActionImports:opt.RemoveActionImports, + removeFunctionImports: opt.RemoveFunctionImports); trimmer.AnalyzeFile(); } From e3e2b69d8c11f04a1bbe45b0dcb2a845f8f4dd4d Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 17:26:15 +0300 Subject: [PATCH 06/15] added --RemoveComplexTypes option --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 20 ++++++++++++++++---- EDMXTrimmer/EDMXTrimmer/Program.cs | 12 ++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 20ce007..41b6417 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -15,7 +15,8 @@ class EdmxTrimmer public bool EntitiesAreRegularExpressions { get; private set; } public bool RemovePrimaryAnnotationsFlag { get; private set; } public bool RemoveActionImportsFlag { get; private set; } - private bool RemoveFunctionImportsFlag { get; } + public bool RemoveFunctionImportsFlag { get; init; } + public bool RemoveComplexTypesFlag { get; init; } public string OutputFileName { get; set; } private XmlDocument _xmlDocument; @@ -34,6 +35,7 @@ class EdmxTrimmer private const string TAG_ENUM_TYPE = "EnumType"; private const string TAG_ACTION_IMPORT = "ActionImport"; private const string TAG_FUNCTION_IMPORT = "FunctionImport"; + private const string TAG_COMPLEXTYPE = "ComplexType"; private const string ATTRIBUTE_ALIAS = "Alias"; private const string ATTRIBUTE_NAMESPACE = "Namespace"; private const string ATTRIBUTE_NAME = "Name"; @@ -51,8 +53,7 @@ public EdmxTrimmer( List entitiesToExclude = null, bool entitiesAreRegularExpressions = false, bool removePrimaryAnnotations = false, - bool removeActionImports = false, - bool removeFunctionImports = false) + bool removeActionImports = false) { this.EdmxFile = edmxFile; this.Verbose = verbose; @@ -73,7 +74,6 @@ public EdmxTrimmer( this.EntitiesAreRegularExpressions = entitiesAreRegularExpressions; this.RemovePrimaryAnnotationsFlag = removePrimaryAnnotations; this.RemoveActionImportsFlag = removeActionImports; - RemoveFunctionImportsFlag = removeFunctionImports; this.LoadFile(); } @@ -127,6 +127,10 @@ public void AnalyzeFile() { RemoveFunctionImports(); } + if (this.RemoveComplexTypesFlag) + { + RemoveComplexTypes(); + } this._xmlDocument.Save(OutputFileName); Console.WriteLine($"Trimmed EDMX saved to file: {OutputFileName}"); @@ -322,6 +326,14 @@ private void RemoveFunctionImports() .ForEach(n => n.ParentNode.RemoveChild(n)); } + private void RemoveComplexTypes() + { + _xmlDocument.GetElementsByTagName(TAG_COMPLEXTYPE) + .Cast() + .ToList() + .ForEach(n => n.ParentNode.RemoveChild(n)); + } + private bool EntityExists(XmlNode xmlNode, string entityType) { var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; diff --git a/EDMXTrimmer/EDMXTrimmer/Program.cs b/EDMXTrimmer/EDMXTrimmer/Program.cs index 4bc7976..34c44c3 100644 --- a/EDMXTrimmer/EDMXTrimmer/Program.cs +++ b/EDMXTrimmer/EDMXTrimmer/Program.cs @@ -59,6 +59,12 @@ public class Options HelpText = "Function imports are removed from the EDMX file", Default = false)] public bool RemoveFunctionImports { get; set; } + + [Option( + Required = false, + HelpText = "ComplexType nodes are removed from the EDMX file", + Default = false)] + public bool RemoveComplexTypes { get; set; } } class Program @@ -78,8 +84,10 @@ static void Main(string[] args) entitiesToExclude:opt.EntitiesToExclude.ToList(), entitiesAreRegularExpressions:opt.EntitiesAreRegularExpressions, removePrimaryAnnotations:opt.RemovePrimaryAnnotations, - removeActionImports:opt.RemoveActionImports, - removeFunctionImports: opt.RemoveFunctionImports); + removeActionImports:opt.RemoveActionImports) { + RemoveComplexTypesFlag = opt.RemoveComplexTypes, + RemoveFunctionImportsFlag = opt.RemoveFunctionImports + }; trimmer.AnalyzeFile(); } From b72fefbe545c1c2c32d6e62aca96d45412b206a6 Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 18:05:08 +0300 Subject: [PATCH 07/15] refactoring --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 76 ++++++++++++++++---------- EDMXTrimmer/EDMXTrimmer/Program.cs | 11 +--- 2 files changed, 50 insertions(+), 37 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 80ef74e..34dc600 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -19,8 +19,7 @@ class EdmxTrimmer public bool RemoveComplexTypesFlag { get; init; } public string OutputFileName { get; set; } - public IReadOnlyCollection? ComplexTypesToKeep { get; private set; } - public IReadOnlyCollection? ComplexTypesToExclude { get; private set; } + public IReadOnlyCollection ActionsToInclude { get; set; } private XmlDocument _xmlDocument; private XmlNode _firstSchemaNode; @@ -56,9 +55,7 @@ public EdmxTrimmer( List entitiesToExclude = null, bool entitiesAreRegularExpressions = false, bool removePrimaryAnnotations = false, - bool removeActionImports = false, - IReadOnlyCollection complexTypesToKeep = null, - IReadOnlyCollection complexTypesToExclude = null) + bool removeActionImports = false) { this.EdmxFile = edmxFile; this.Verbose = verbose; @@ -80,9 +77,6 @@ public EdmxTrimmer( this.RemovePrimaryAnnotationsFlag = removePrimaryAnnotations; this.RemoveActionImportsFlag = removeActionImports; - ComplexTypesToKeep = complexTypesToKeep; - ComplexTypesToExclude = complexTypesToExclude; - this.LoadFile(); } @@ -229,19 +223,25 @@ private void RemoveEntitySets(IEnumerable entitySets, List ent private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOnlyCollection entitiesNamesToKeep) { // Remove all navigation properties - this._xmlDocument.GetElementsByTagName(TAG_NAVIGATION_PROPERTY).Cast() - .Where(navProp => !entitiesNamesToKeep.Any(entityType => EntityExists(navProp, entityType))).ToList() - .ForEach(n => n.ParentNode.RemoveChild(n)); + RemoveNodes(_xmlDocument + .GetElementsByTagName(TAG_NAVIGATION_PROPERTY) + .Cast() + .Where(navProp => !entitiesNamesToKeep.Any(entityType => NodeReferencesEntity(navProp, entityType))) + ); // Remove entity not required (EntityType) var entityTypesToKeep = entityTypes.Where(n => entitiesNamesToKeep.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); - entityTypes.Except(entityTypesToKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); + RemoveNodes(entityTypes.Except(entityTypesToKeep)); // Remove all Actions - this._xmlDocument.GetElementsByTagName(TAG_ACTION).Cast() - .Where(action => !entitiesNamesToKeep.Any(entityType => action.ChildNodes.Cast(). - Any(childNode => EntityExists(childNode, entityType)))).ToList() - .ForEach(n => n.ParentNode.RemoveChild(n)); + var allActions = _xmlDocument + .GetElementsByTagName(TAG_ACTION) + .Cast(); + + var actionsToRemove = allActions + .Where(actionNode => !ActionReferencesAnyEntity(actionNode)/* && false == ActionIsWhitelisted(actionNode)*/); + + RemoveNodes(actionsToRemove); // Determine enums to keep List enumTypesFound = new List(); @@ -298,8 +298,32 @@ string RemoveNamespace(XmlNode xmlNode) { return null; } + + bool? ActionIsWhitelisted(XmlNode xmlNode) { + return null; + } + + bool ActionReferencesAnyEntity(XmlNode actionNode) => entitiesNamesToKeep.Any(entityType => AnyChildReferencesEntity(actionNode, entityType)); + + bool AnyChildReferencesEntity(XmlNode action, string entityType) => + action + .ChildNodes + .Cast() + .Any(childNode => NodeReferencesEntity(childNode, entityType)); + + bool NodeReferencesEntity(XmlNode xmlNode, string entityType) { + var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; + + if(null == typeValue) { + return false; + } + if(ENTITYNAMESPACE_ALIAS != null && IsEntityTypeMatches(entityType, ENTITYNAMESPACE_ALIAS, typeValue)) { + return true; + } + return IsEntityTypeMatches(entityType, ENTITYNAMESPACE, typeValue); + } } - + private string GetEntityTypeWithoutNamespace(XmlNode n, string attributeName) { var entityType = n.Attributes[attributeName]?.Value; @@ -342,18 +366,6 @@ private void RemoveComplexTypes() .ForEach(n => n.ParentNode.RemoveChild(n)); } - private bool EntityExists(XmlNode xmlNode, string entityType) { - var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; - - if(null == typeValue) { - return false; - } - if(ENTITYNAMESPACE_ALIAS != null && IsEntityTypeMatches(entityType, ENTITYNAMESPACE_ALIAS, typeValue)) { - return true; - } - return IsEntityTypeMatches(entityType, ENTITYNAMESPACE, typeValue); - } - private bool IsEntityTypeMatches(string entityType, string @namespace, string source) { var key = @namespace + entityType; @@ -365,5 +377,11 @@ private bool IsEntityTypeMatches(string entityType, string @namespace, string so return regex.IsMatch(source!); } + + private static void RemoveNodes(IEnumerable nodesToRemove) { + foreach(var node in nodesToRemove!.ToList()) { + node.ParentNode.RemoveChild(node); + } + } } } \ No newline at end of file diff --git a/EDMXTrimmer/EDMXTrimmer/Program.cs b/EDMXTrimmer/EDMXTrimmer/Program.cs index b2b6164..41324ce 100644 --- a/EDMXTrimmer/EDMXTrimmer/Program.cs +++ b/EDMXTrimmer/EDMXTrimmer/Program.cs @@ -75,13 +75,7 @@ public class Options Required = false, HelpText = $"Enter action names to keep, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", Separator = ',')] - public IReadOnlyCollection ComplexTypesToKeep { get; set; } - - [Option( - Required = false, - HelpText = $"Enter action names to exclude, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", - Separator = ',')] - public IReadOnlyCollection ComplexTypesToExclude { get; set; } + public IReadOnlyCollection ActionsToKeep { get; set; } } class Program { @@ -102,7 +96,8 @@ static void Main(string[] args) removePrimaryAnnotations:opt.RemovePrimaryAnnotations, removeActionImports:opt.RemoveActionImports) { RemoveComplexTypesFlag = opt.RemoveComplexTypes, - RemoveFunctionImportsFlag = opt.RemoveFunctionImports + RemoveFunctionImportsFlag = opt.RemoveFunctionImports, + ActionsToInclude = opt.ActionsToKeep }; trimmer.AnalyzeFile(); From 4a73892b686d2e5cc7bb5b820955503dd928b8bf Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 19:12:38 +0300 Subject: [PATCH 08/15] added actions whitelist --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 43 ++++++++++++++++++-------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 34dc600..3786fad 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -238,8 +238,12 @@ private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOn .GetElementsByTagName(TAG_ACTION) .Cast(); - var actionsToRemove = allActions - .Where(actionNode => !ActionReferencesAnyEntity(actionNode)/* && false == ActionIsWhitelisted(actionNode)*/); + var actionsToRemove = allActions.Where(actionNode => { + if(ActionsToInclude != null && ActionsToInclude.Any()) { + return true != ActionIsWhitelisted(GetActionName(actionNode)); + } + return !ActionReferencesAnyEntity(actionNode); + }); RemoveNodes(actionsToRemove); @@ -274,7 +278,7 @@ private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOn return; - IReadOnlyCollection GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => + IEnumerable GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => typeNode .ChildNodes .Cast() @@ -298,9 +302,13 @@ string RemoveNamespace(XmlNode xmlNode) { return null; } - - bool? ActionIsWhitelisted(XmlNode xmlNode) { - return null; + + bool ActionIsWhitelisted(string? actionName) { + if(null == actionName || null == ActionsToInclude || !ActionsToInclude.Any()) { + return false; + } + var regex = GetRegexOrCreate("ACTIONS-TO-INCLUDE", () => EntitySearchTermsToRegularExpression(ActionsToInclude)); + return regex.IsMatch(actionName); } bool ActionReferencesAnyEntity(XmlNode actionNode) => entitiesNamesToKeep.Any(entityType => AnyChildReferencesEntity(actionNode, entityType)); @@ -324,6 +332,8 @@ bool NodeReferencesEntity(XmlNode xmlNode, string entityType) { } } + private static string GetActionName(XmlNode actionNode) => actionNode.Attributes[ATTRIBUTE_NAME]?.Value; + private string GetEntityTypeWithoutNamespace(XmlNode n, string attributeName) { var entityType = n.Attributes[attributeName]?.Value; @@ -367,17 +377,24 @@ private void RemoveComplexTypes() } private bool IsEntityTypeMatches(string entityType, string @namespace, string source) { - var key = @namespace + entityType; + var key = "ENTITY-" + @namespace + entityType; + var regex = GetRegexOrCreate(key, () => Regex.Escape(@namespace + entityType) + "\\)?$"); + + return regex.IsMatch(source!); + } - if(!entityTypeRegexps.TryGetValue(key, out var regex)) { - var pattern = Regex.Escape(@namespace + entityType) + "\\)?$"; - regex = new Regex(pattern); - entityTypeRegexps.Add(key, regex); + private Regex GetRegexOrCreate(string key, Func patternFactory) { + if(entityTypeRegexps.TryGetValue(key!, out var cached)) { + return cached; } + + var pattern = patternFactory(); + var target = new Regex(pattern!); - return regex.IsMatch(source!); + entityTypeRegexps.Add(key, target); + return target; } - + private static void RemoveNodes(IEnumerable nodesToRemove) { foreach(var node in nodesToRemove!.ToList()) { node.ParentNode.RemoveChild(node); From 9da6311e54b3374ccf1b00499d860d6b42ff1e04 Mon Sep 17 00:00:00 2001 From: AndrewK Date: Thu, 26 Oct 2023 18:05:08 +0300 Subject: [PATCH 09/15] added actions whitelist --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 105 ++++++++++++++++--------- EDMXTrimmer/EDMXTrimmer/Program.cs | 11 +-- 2 files changed, 73 insertions(+), 43 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 80ef74e..3786fad 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -19,8 +19,7 @@ class EdmxTrimmer public bool RemoveComplexTypesFlag { get; init; } public string OutputFileName { get; set; } - public IReadOnlyCollection? ComplexTypesToKeep { get; private set; } - public IReadOnlyCollection? ComplexTypesToExclude { get; private set; } + public IReadOnlyCollection ActionsToInclude { get; set; } private XmlDocument _xmlDocument; private XmlNode _firstSchemaNode; @@ -56,9 +55,7 @@ public EdmxTrimmer( List entitiesToExclude = null, bool entitiesAreRegularExpressions = false, bool removePrimaryAnnotations = false, - bool removeActionImports = false, - IReadOnlyCollection complexTypesToKeep = null, - IReadOnlyCollection complexTypesToExclude = null) + bool removeActionImports = false) { this.EdmxFile = edmxFile; this.Verbose = verbose; @@ -80,9 +77,6 @@ public EdmxTrimmer( this.RemovePrimaryAnnotationsFlag = removePrimaryAnnotations; this.RemoveActionImportsFlag = removeActionImports; - ComplexTypesToKeep = complexTypesToKeep; - ComplexTypesToExclude = complexTypesToExclude; - this.LoadFile(); } @@ -229,19 +223,29 @@ private void RemoveEntitySets(IEnumerable entitySets, List ent private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOnlyCollection entitiesNamesToKeep) { // Remove all navigation properties - this._xmlDocument.GetElementsByTagName(TAG_NAVIGATION_PROPERTY).Cast() - .Where(navProp => !entitiesNamesToKeep.Any(entityType => EntityExists(navProp, entityType))).ToList() - .ForEach(n => n.ParentNode.RemoveChild(n)); + RemoveNodes(_xmlDocument + .GetElementsByTagName(TAG_NAVIGATION_PROPERTY) + .Cast() + .Where(navProp => !entitiesNamesToKeep.Any(entityType => NodeReferencesEntity(navProp, entityType))) + ); // Remove entity not required (EntityType) var entityTypesToKeep = entityTypes.Where(n => entitiesNamesToKeep.Contains(n.Attributes[ATTRIBUTE_NAME].Value)).ToList(); - entityTypes.Except(entityTypesToKeep).ToList().ForEach(n => n.ParentNode.RemoveChild(n)); + RemoveNodes(entityTypes.Except(entityTypesToKeep)); // Remove all Actions - this._xmlDocument.GetElementsByTagName(TAG_ACTION).Cast() - .Where(action => !entitiesNamesToKeep.Any(entityType => action.ChildNodes.Cast(). - Any(childNode => EntityExists(childNode, entityType)))).ToList() - .ForEach(n => n.ParentNode.RemoveChild(n)); + var allActions = _xmlDocument + .GetElementsByTagName(TAG_ACTION) + .Cast(); + + var actionsToRemove = allActions.Where(actionNode => { + if(ActionsToInclude != null && ActionsToInclude.Any()) { + return true != ActionIsWhitelisted(GetActionName(actionNode)); + } + return !ActionReferencesAnyEntity(actionNode); + }); + + RemoveNodes(actionsToRemove); // Determine enums to keep List enumTypesFound = new List(); @@ -274,7 +278,7 @@ private void RemoveEntityTypes(IReadOnlyCollection entityTypes, IReadOn return; - IReadOnlyCollection GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => + IEnumerable GetEntityTypesFromNodeChildren(XmlNode typeNode, string nodeName) => typeNode .ChildNodes .Cast() @@ -298,8 +302,38 @@ string RemoveNamespace(XmlNode xmlNode) { return null; } + + bool ActionIsWhitelisted(string? actionName) { + if(null == actionName || null == ActionsToInclude || !ActionsToInclude.Any()) { + return false; + } + var regex = GetRegexOrCreate("ACTIONS-TO-INCLUDE", () => EntitySearchTermsToRegularExpression(ActionsToInclude)); + return regex.IsMatch(actionName); + } + + bool ActionReferencesAnyEntity(XmlNode actionNode) => entitiesNamesToKeep.Any(entityType => AnyChildReferencesEntity(actionNode, entityType)); + + bool AnyChildReferencesEntity(XmlNode action, string entityType) => + action + .ChildNodes + .Cast() + .Any(childNode => NodeReferencesEntity(childNode, entityType)); + + bool NodeReferencesEntity(XmlNode xmlNode, string entityType) { + var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; + + if(null == typeValue) { + return false; + } + if(ENTITYNAMESPACE_ALIAS != null && IsEntityTypeMatches(entityType, ENTITYNAMESPACE_ALIAS, typeValue)) { + return true; + } + return IsEntityTypeMatches(entityType, ENTITYNAMESPACE, typeValue); + } } - + + private static string GetActionName(XmlNode actionNode) => actionNode.Attributes[ATTRIBUTE_NAME]?.Value; + private string GetEntityTypeWithoutNamespace(XmlNode n, string attributeName) { var entityType = n.Attributes[attributeName]?.Value; @@ -342,28 +376,29 @@ private void RemoveComplexTypes() .ForEach(n => n.ParentNode.RemoveChild(n)); } - private bool EntityExists(XmlNode xmlNode, string entityType) { - var typeValue = xmlNode.Attributes[ATTRIBUTE_TYPE]?.Value; + private bool IsEntityTypeMatches(string entityType, string @namespace, string source) { + var key = "ENTITY-" + @namespace + entityType; + var regex = GetRegexOrCreate(key, () => Regex.Escape(@namespace + entityType) + "\\)?$"); + + return regex.IsMatch(source!); + } - if(null == typeValue) { - return false; - } - if(ENTITYNAMESPACE_ALIAS != null && IsEntityTypeMatches(entityType, ENTITYNAMESPACE_ALIAS, typeValue)) { - return true; + private Regex GetRegexOrCreate(string key, Func patternFactory) { + if(entityTypeRegexps.TryGetValue(key!, out var cached)) { + return cached; } - return IsEntityTypeMatches(entityType, ENTITYNAMESPACE, typeValue); - } - private bool IsEntityTypeMatches(string entityType, string @namespace, string source) { - var key = @namespace + entityType; + var pattern = patternFactory(); + var target = new Regex(pattern!); + + entityTypeRegexps.Add(key, target); + return target; + } - if(!entityTypeRegexps.TryGetValue(key, out var regex)) { - var pattern = Regex.Escape(@namespace + entityType) + "\\)?$"; - regex = new Regex(pattern); - entityTypeRegexps.Add(key, regex); + private static void RemoveNodes(IEnumerable nodesToRemove) { + foreach(var node in nodesToRemove!.ToList()) { + node.ParentNode.RemoveChild(node); } - - return regex.IsMatch(source!); } } } \ No newline at end of file diff --git a/EDMXTrimmer/EDMXTrimmer/Program.cs b/EDMXTrimmer/EDMXTrimmer/Program.cs index b2b6164..41324ce 100644 --- a/EDMXTrimmer/EDMXTrimmer/Program.cs +++ b/EDMXTrimmer/EDMXTrimmer/Program.cs @@ -75,13 +75,7 @@ public class Options Required = false, HelpText = $"Enter action names to keep, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", Separator = ',')] - public IReadOnlyCollection ComplexTypesToKeep { get; set; } - - [Option( - Required = false, - HelpText = $"Enter action names to exclude, works with \"{EntitiesToKeepName}\" and \"{EntitiesToExcludeName}\" options. All values to be separated with commas. Supports ? and * wildcards.", - Separator = ',')] - public IReadOnlyCollection ComplexTypesToExclude { get; set; } + public IReadOnlyCollection ActionsToKeep { get; set; } } class Program { @@ -102,7 +96,8 @@ static void Main(string[] args) removePrimaryAnnotations:opt.RemovePrimaryAnnotations, removeActionImports:opt.RemoveActionImports) { RemoveComplexTypesFlag = opt.RemoveComplexTypes, - RemoveFunctionImportsFlag = opt.RemoveFunctionImports + RemoveFunctionImportsFlag = opt.RemoveFunctionImports, + ActionsToInclude = opt.ActionsToKeep }; trimmer.AnalyzeFile(); From a7111c70334667f8b8eb6a6f343e7527fcd101ad Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sat, 11 Nov 2023 12:20:13 +0100 Subject: [PATCH 10/15] refactor boolean parameter in filter method --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 45 ++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 3786fad..dc3b9d7 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -149,7 +149,8 @@ private void RemoveAllEntitiesExcept( List entitySets, List entityTypes) { - var (entitySetsToKeep, entityTypeNamesToKeep) = FilterByEntity(entitiesToKeep, entitySets, entityTypes, true); + var (entitySetsToKeep, entityTypeNamesToKeep) = + FilterByEntityIncluding(entitiesToKeep, entitySets, entityTypes); RemoveEntitySets(entitySets, entitySetsToKeep); RemoveEntityTypes(entityTypes, entityTypeNamesToKeep); @@ -160,13 +161,51 @@ private void RemoveExcludedEntities( List entitySets, List entityTypes) { - var (entitySetsToKeep, entityTypeNamesToKeep) = FilterByEntity(entitiesToExclude, entitySets, entityTypes, false); + var (entitySetsToKeep, entityTypeNamesToKeep) = + FilterByEntityExcluding(entitiesToExclude, entitySets, entityTypes); RemoveEntitySets(entitySets, entitySetsToKeep); RemoveEntityTypes(entityTypes, entityTypeNamesToKeep); } - private (List EntitySetsToKeep, IReadOnlyCollection EntityTypeNamesToKeep) FilterByEntity(IEnumerable filteringEntities, IEnumerable entitySets, IEnumerable entityTypes, bool includeFiltered) + /// + /// Returns a list of EntitySets and EntityTypes that match the filteringEntities. + /// + /// The entities to filter by. + /// The entity sets to filter. + /// The entities to filter. + /// A tuple of filtered entity sets and filtered entity names. + internal (List EntitySetsToKeep, IReadOnlyCollection EntityTypeNamesToKeep) + FilterByEntityIncluding( + IEnumerable filteringEntities, + IEnumerable entitySets, + IEnumerable entityTypes) + { + return FilterByEntity(filteringEntities, entitySets, entityTypes, true); + } + + /// + /// Returns a list of EntitySets and EntityTypes that do not match the filteringEntities. + /// + /// The entities that should not be part of the result. + /// The entity sets to filter. + /// The entities to filter. + /// A tuple of filtered entity sets and filtered entity names. + internal (List EntitySetsToKeep, IReadOnlyCollection EntityTypeNamesToKeep) + FilterByEntityExcluding( + IEnumerable filteringEntities, + IEnumerable entitySets, + IEnumerable entityTypes) + { + return FilterByEntity(filteringEntities, entitySets, entityTypes, false); + } + + private (List EntitySetsToKeep, IReadOnlyCollection EntityTypeNamesToKeep) + FilterByEntity( + IEnumerable filteringEntities, + IEnumerable entitySets, + IEnumerable entityTypes, + bool includeFiltered) { var nameRegex = EntitySearchTermsToRegularExpression(filteringEntities); From a46b03c22c1105d95c094302a1f09b0cdc39e26c Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sat, 11 Nov 2023 14:08:26 +0100 Subject: [PATCH 11/15] add unit tests --- EDMXTrimmer/EDMXTrimmer.sln | 6 + EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 2 +- .../EDMXTrimmerTest/EDMXTrimmerTest.csproj | 30 ++ .../EDMXTrimmerTest/EdmxTrimmerTest.cs | 168 +++++++++ EDMXTrimmer/EDMXTrimmerTest/GlobalUsings.cs | 1 + EDMXTrimmer/EDMXTrimmerTest/TripPin.xml | 339 ++++++++++++++++++ 6 files changed, 545 insertions(+), 1 deletion(-) create mode 100644 EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj create mode 100644 EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs create mode 100644 EDMXTrimmer/EDMXTrimmerTest/GlobalUsings.cs create mode 100644 EDMXTrimmer/EDMXTrimmerTest/TripPin.xml diff --git a/EDMXTrimmer/EDMXTrimmer.sln b/EDMXTrimmer/EDMXTrimmer.sln index 40d6ea7..a644a06 100644 --- a/EDMXTrimmer/EDMXTrimmer.sln +++ b/EDMXTrimmer/EDMXTrimmer.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.28803.202 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDMXTrimmer", "EDMXTrimmer\EDMXTrimmer.csproj", "{CEE5566E-5FFF-46E4-BC78-166980691951}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EDMXTrimmerTest", "EDMXTrimmerTest\EDMXTrimmerTest.csproj", "{D555AE09-A8C1-4368-AA62-67AE517230B4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {CEE5566E-5FFF-46E4-BC78-166980691951}.Debug|Any CPU.Build.0 = Debug|Any CPU {CEE5566E-5FFF-46E4-BC78-166980691951}.Release|Any CPU.ActiveCfg = Release|Any CPU {CEE5566E-5FFF-46E4-BC78-166980691951}.Release|Any CPU.Build.0 = Release|Any CPU + {D555AE09-A8C1-4368-AA62-67AE517230B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D555AE09-A8C1-4368-AA62-67AE517230B4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D555AE09-A8C1-4368-AA62-67AE517230B4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D555AE09-A8C1-4368-AA62-67AE517230B4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index dc3b9d7..885c858 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -6,7 +6,7 @@ namespace EDMXTrimmer { - class EdmxTrimmer + public class EdmxTrimmer { public string EdmxFile { get; private set; } public bool Verbose { get; private set; } diff --git a/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj b/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj new file mode 100644 index 0000000..868861f --- /dev/null +++ b/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj @@ -0,0 +1,30 @@ + + + + net7.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + Always + + + + diff --git a/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs b/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs new file mode 100644 index 0000000..496bd05 --- /dev/null +++ b/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs @@ -0,0 +1,168 @@ +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using EDMXTrimmer; + +namespace EDMXTrimmer.Tests +{ + public class EdmxTrimmerTests + { + private const string TestEdmxFile = "TripPin.xml"; + private const string TestOutputFile = "TripPin-Trimmed.xml"; + + [SetUp] + public void Setup() + { + + } + + [TearDown] + public void TearDown() + { + // Delete the test EDMX file and output file + File.Delete(TestOutputFile); + } + + [Test] + public void TestEdmxTrimmer() + { + // Arrange + var entitiesToKeep = new List { "Photos", "People" }; + var entitiesToExclude = new List { "People" }; + var edmxTrimmer = new EdmxTrimmer(TestEdmxFile, TestOutputFile, true, entitiesToKeep, entitiesToExclude); + + // Act + edmxTrimmer.AnalyzeFile(); + + // Assert + Assert.That(File.Exists(TestOutputFile), Is.True); + + var trimmedEdmx = File.ReadAllText(TestOutputFile); + Assert.Multiple(() => + { + Assert.That(trimmedEdmx, Does.Contain(" { @"\b\w+s\b" }; // Keep entities that end with "s" + var entitiesToExclude = new List { @"P\w+" }; // Exclude entities that start with "P" + var edmxTrimmer = new EdmxTrimmer( + TestEdmxFile, + TestOutputFile, + entitiesAreRegularExpressions: true, + entitiesToKeep: entitiesToKeep, + entitiesToExclude: entitiesToExclude); + + // Act + edmxTrimmer.AnalyzeFile(); + + // Assert + Assert.That(File.Exists(TestOutputFile), Is.True); + + var trimmedEdmx = File.ReadAllText(TestOutputFile); + Assert.Multiple(() => + { + Assert.That(trimmedEdmx, Does.Not.Contain(" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + image/jpeg + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Core.V1.Permission/Read + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + + + + Concurrency + + + + + + + Org.OData.Capabilities.V1.NavigationType/None + + + + + + + + Org.OData.Capabilities.V1.NavigationType/Recursive + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + Trips + Friends + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.SearchExpressions/none + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Org.OData.Capabilities.V1.ConformanceLevelType/Advanced + + + + + application/json;odata.metadata=full;IEEE754Compatible=false;odata.streaming=true + + application/json;odata.metadata=minimal;IEEE754Compatible=false;odata.streaming=true + + application/json;odata.metadata=none;IEEE754Compatible=false;odata.streaming=true + + + + + + + contains + endswith + startswith + length + indexof + substring + tolower + toupper + trim + concat + year + month + day + hour + minute + second + round + floor + ceiling + cast + isof + + + + + + \ No newline at end of file From 585785e37d09968472169557f47f9d8984aece1c Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sat, 11 Nov 2023 14:41:57 +0100 Subject: [PATCH 12/15] fix regression from new filter logic --- EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs | 27 +++++++++++++++---- .../EDMXTrimmerTest/EdmxTrimmerTest.cs | 8 ++++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs index 885c858..326b99b 100644 --- a/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs +++ b/EDMXTrimmer/EDMXTrimmer/EdmxTrimmer.cs @@ -209,20 +209,37 @@ private void RemoveExcludedEntities( { var nameRegex = EntitySearchTermsToRegularExpression(filteringEntities); - var entitySetsNodes = entitySets + // remove items from entityTypes that are referenced by entitySets + // those entityTypes will be filtered via the entitySets filtering + var entityTypeNamesReferencedByEntitySets = entitySets + .Select(n => GetEntityTypeWithoutNamespace(n, TAG_ENTITY_TYPE)) + .ToList(); + var entityTypesNotReferencedByEntitySets = entityTypes + .Where(node => !entityTypeNamesReferencedByEntitySets.Contains(GetEntityTypeWithoutNamespace(node, ATTRIBUTE_NAME))) + .ToList(); + + // filter entitySets + var entitySetsFiltered = entitySets .Where(n => Regex.IsMatch(n.Attributes[ATTRIBUTE_NAME].Value, nameRegex) ? includeFiltered : !includeFiltered) .ToList(); - var entityTypeNames = entitySetsNodes + // filter entityTypes not referenced by entitySets + var entityTypesFiltered = entityTypesNotReferencedByEntitySets + .Where(node => Regex.IsMatch(node.Attributes[ATTRIBUTE_NAME].Value, nameRegex) ? includeFiltered : !includeFiltered) + .ToList(); + + // combine names of + // - entityTypes referenced by filtered entitySets + // - filtered entityTypes not referenced by entitySets + var entityTypeNames = entitySetsFiltered .Select(n => GetEntityTypeWithoutNamespace(n, TAG_ENTITY_TYPE)) - .Concat(entityTypes - .Where(node => Regex.IsMatch(node.Attributes[ATTRIBUTE_NAME].Value, nameRegex) ? includeFiltered : !includeFiltered) + .Concat(entityTypesFiltered .Select(node => GetEntityTypeWithoutNamespace(node, ATTRIBUTE_NAME)) ) .Distinct() .ToList(); - return (entitySetsNodes, entityTypeNames); + return (entitySetsFiltered, entityTypeNames); } private string EntitySearchTermsToRegularExpression(IEnumerable entitiesToKeep) diff --git a/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs b/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs index 496bd05..2896613 100644 --- a/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs +++ b/EDMXTrimmer/EDMXTrimmerTest/EdmxTrimmerTest.cs @@ -42,9 +42,13 @@ public void TestEdmxTrimmer() Assert.Multiple(() => { Assert.That(trimmedEdmx, Does.Contain(" { Assert.That(trimmedEdmx, Does.Not.Contain(" Date: Sat, 11 Nov 2023 15:15:06 +0100 Subject: [PATCH 13/15] add test step to GitHub workflow --- .github/workflows/pull-request-build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-request-build.yml b/.github/workflows/pull-request-build.yml index f5a0b6e..bbdf5de 100644 --- a/.github/workflows/pull-request-build.yml +++ b/.github/workflows/pull-request-build.yml @@ -13,6 +13,8 @@ jobs: - uses: actions/checkout@v3 - name: Build run: dotnet build --configuration Release EDMXTrimmer + - name: Test + run: dotnet test --configuration Release EDMXTrimmer - name: Publish run: dotnet publish --configuration Release EDMXTrimmer --output zip - name: Upload EDMXTrimmer.zip From 6ef78e9861c0e067659535c2ab1e65c34236197e Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sat, 11 Nov 2023 15:31:50 +0100 Subject: [PATCH 14/15] add NUnit test logger and publish test results --- .github/workflows/pull-request-build.yml | 9 +++++++-- EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request-build.yml b/.github/workflows/pull-request-build.yml index bbdf5de..86b41d9 100644 --- a/.github/workflows/pull-request-build.yml +++ b/.github/workflows/pull-request-build.yml @@ -14,9 +14,14 @@ jobs: - name: Build run: dotnet build --configuration Release EDMXTrimmer - name: Test - run: dotnet test --configuration Release EDMXTrimmer + run: dotnet test --logger:nunit --configuration Release EDMXTrimmer + - name: Publish Test Results + uses: EnricoMi/publish-unit-test-result-action@v2 + with: + files: '**/TestResults/*.xml' + comment_mode: 'off' - name: Publish - run: dotnet publish --configuration Release EDMXTrimmer --output zip + run: dotnet publish --configuration Release EDMXTrimmer/EDMXTrimmer --output zip - name: Upload EDMXTrimmer.zip uses: actions/upload-artifact@v3 with: diff --git a/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj b/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj index 868861f..7cd0bb3 100644 --- a/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj +++ b/EDMXTrimmer/EDMXTrimmerTest/EDMXTrimmerTest.csproj @@ -14,6 +14,7 @@ + From e4803124d0f6bfb18422d7a02b45f5e7d39fd23f Mon Sep 17 00:00:00 2001 From: Florian Hopfner Date: Sat, 11 Nov 2023 15:36:41 +0100 Subject: [PATCH 15/15] update publish-unit-test-result-action to use composite action --- .github/workflows/pull-request-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request-build.yml b/.github/workflows/pull-request-build.yml index 86b41d9..7d97515 100644 --- a/.github/workflows/pull-request-build.yml +++ b/.github/workflows/pull-request-build.yml @@ -16,7 +16,7 @@ jobs: - name: Test run: dotnet test --logger:nunit --configuration Release EDMXTrimmer - name: Publish Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 + uses: EnricoMi/publish-unit-test-result-action/composite@v2 with: files: '**/TestResults/*.xml' comment_mode: 'off'