diff --git a/.gitignore b/.gitignore index 133aef4d..c39f0081 100644 --- a/.gitignore +++ b/.gitignore @@ -192,3 +192,4 @@ ModelManifest.xml /tmp /.vs/config/applicationhost.config /.vs +/QEx samples diff --git a/FetchXmlBuilder/Converters/QExFactory.cs b/FetchXmlBuilder/Converters/QExFactory.cs new file mode 100644 index 00000000..2f4c8855 --- /dev/null +++ b/FetchXmlBuilder/Converters/QExFactory.cs @@ -0,0 +1,269 @@ +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Rappen.XTB.FetchXmlBuilder.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Cinteros.Xrm.FetchXmlBuilder.Converters +{ + internal class QExFactory + { + private readonly QueryExpressionCodeGenerator gen; + private readonly bool objectini; + private readonly bool comments; + private readonly string CRLF; + private readonly string Indent; + private Dictionary entityaliases; + + internal QExFactory(QueryExpressionCodeGenerator generator) + { + gen = generator ?? throw new ArgumentNullException(nameof(generator)); + objectini = gen.settings.ObjectInitializer; + comments = gen.settings.IncludeComments; + CRLF = QueryExpressionCodeGenerator.CRLF; + Indent = QueryExpressionCodeGenerator.Indent; + } + + internal string Generated => CreateCode(gen.qex); + + private string CreateCode(QueryExpression qex) + { + entityaliases = new Dictionary(); + var code = new StringBuilder(); + var qename = gen.GetVarName("qefactory"); + code.Append(GetQueryCodeStart(qename, qex)); + if (!objectini) + { + code.Append(GetColumns(qex.EntityName, qex.ColumnSet, qename)); + code.Append(GetConditions(qex.EntityName, qex.Criteria.Conditions, qename)); + code.Append(GetFilters(qex.EntityName, qex.Criteria.Filters, qename, "Criteria")); + code.Append(GetOrders(qex.EntityName, qex.Orders, qename, true)); + code.Append(GetLinkEntities(qex.LinkEntities, qename)); + } + var codestr = gen.ReplaceValueTokens(code.ToString()); + return codestr; + } + + private string GetQueryCodeStart(string qename, QueryExpression qex) + { + var querycode = string.Empty; + if (comments) + { + querycode += "// Instantiate QueryExpressionFactory " + qename + CRLF; + } + switch (gen.settings.QExFlavor) + { + case QExFlavorEnum.EarlyBound: + querycode += $"var {qename} = QueryExpressionFactory.Create<{gen.GetCodeEntity(qex.EntityName)}>("; + break; + + default: + querycode += $"var {qename} = QueryExpressionFactory.Create({gen.GetCodeEntity(qex.EntityName)}"; + break; + } + + querycode += QueryExpressionCodeGenerator.GetQueryOptions(qex); + + var columns = GetColumns(qex.EntityName, qex.ColumnSet, "ColumnSet", 2); + if (!string.IsNullOrEmpty(columns)) + { + querycode += ", " + CRLF + columns; + } + var filters = GetConditions(qex.EntityName, qex.Criteria.Conditions, qename); + if (!string.IsNullOrEmpty(filters)) + { + querycode += ", " + CRLF + filters; + } + querycode = querycode + ");" + CRLF; + return querycode; + } + + private string GetColumns(string entity, ColumnSet columns, string LineStart, int indents = 1) + { + var code = new StringBuilder(); + if (columns.AllColumns) + { + if (comments) + { + code.AppendLine("// Add all columns to " + LineStart); + } + code.Append("new ColumnSet(true)"); + } + else if (columns.Columns.Count > 0) + { + if (comments) + { + code.AppendLine("// Add columns to " + LineStart); + } + switch (gen.settings.QExFlavor) + { + case QExFlavorEnum.EarlyBound: + LineStart = gen.GetCodeEntityPrefix(entity) + " => new { "; + break; + + default: + LineStart = $"{Indent}new ColumnSet("; + break; + } + var colsEB = QueryExpressionCodeGenerator.GetCodeParametersMaxWidth(120 - LineStart.Length, indents, columns.Columns.Select(c => gen.GetCodeAttribute(entity, c)).ToArray()); + code.Append(LineStart + colsEB); + switch (gen.settings.QExFlavor) + { + case QExFlavorEnum.EarlyBound: + code.Append(" }"); + break; + + default: + code.Append(")"); + break; + } + } + return code.ToString(); + } + + private string GetLinkEntities(DataCollection linkEntities, string LineStart) + { + if (linkEntities?.Count == 0) + { + return string.Empty; + } + var code = new StringBuilder(); + foreach (var link in linkEntities) + { + var linkname = gen.GetVarName(string.IsNullOrEmpty(link.EntityAlias) ? LineStart + "_" + link.LinkToEntityName : link.EntityAlias); + code.AppendLine(); + if (comments) + { + code.AppendLine("// Add link-entity " + linkname); + } + var join = link.JoinOperator == JoinOperator.Inner ? "" : "JoinOperator." + link.JoinOperator.ToString(); + var varstart = + link.LinkEntities.Count > 0 || + link.Columns.Columns.Count > 0 || + link.LinkCriteria.Conditions.Count > 0 || + link.Orders.Count > 0 ? $"var {linkname} = " : String.Empty; + var parms = QueryExpressionCodeGenerator.GetCodeParametersMaxWidth(120 - varstart.Length - LineStart.Length, 1, + gen.GetCodeEntity(link.LinkToEntityName), + gen.GetCodeAttribute(link.LinkFromEntityName, + link.LinkFromAttributeName), + gen.GetCodeAttribute(link.LinkToEntityName, link.LinkToAttributeName), + join); + code.AppendLine($"{varstart}{LineStart}.AddLink({parms});"); + if (!string.IsNullOrWhiteSpace(link.EntityAlias)) + { + entityaliases.Add(link.EntityAlias, link.LinkToEntityName); + code.AppendLine(linkname + ".EntityAlias = \"" + link.EntityAlias + "\";"); + } + code.Append(GetColumns(link.LinkToEntityName, link.Columns, linkname + ".Columns")); + code.Append(GetConditions(link.LinkToEntityName, link.LinkCriteria.Conditions, "")); + code.Append(GetFilters(link.LinkToEntityName, link.LinkCriteria.Filters, linkname, "LinkCriteria")); + code.Append(GetOrders(link.LinkToEntityName, link.Orders, linkname)); + code.Append(GetLinkEntities(link.LinkEntities, linkname)); + } + return code.ToString(); + } + + private string GetFilters(string entity, IEnumerable filters, string parentName, string property) + { + if (filters?.Any() != true) + { + return string.Empty; + } + + var filterscode = new List(); + var i = 0; + foreach (var filter in filters.Where(f => f.Conditions.Any() || f.Filters.Any())) + { + var code = new StringBuilder(); + var LineStart = parentName + (!string.IsNullOrEmpty(property) ? "." + property : ""); + var filtername = LineStart.Replace(".", "_"); + if (filters.Count() > 1) + { + filtername += "_" + i++.ToString(); + } + filtername = gen.GetVarName(filtername); + if (comments) + { + code.AppendLine(); + code.AppendLine("// Add filter " + LineStart); + } + if (!objectini) + { + code.AppendLine($"var {filtername} = new FilterExpression();"); + code.AppendLine($"{LineStart}.AddFilter({filtername});"); + } + if (filter.FilterOperator == LogicalOperator.Or) + { + code.AppendLine(LineStart + ".FilterOperator = LogicalOperator.Or;"); + } + code.AppendLine(GetConditions(entity, filter.Conditions, LineStart)); + code.Append(GetFilters(entity, filter.Filters, filtername, null)); + filterscode.Add(code.ToString()); + } + return string.Join($",{CRLF}", filterscode); + } + + private string GetConditions(string entity, IEnumerable conditions, string LineStart) + { + if (conditions?.Any() != true) + { + return string.Empty; + } + var conditionscode = new List(); + foreach (var cond in conditions) + { + var filterentity = entity; + var entityalias = ""; + var values = ""; + var token = LineStart.Replace(".", "_").Replace("_Criteria", "").Replace("_LinkCriteria", ""); + if (!string.IsNullOrWhiteSpace(cond.EntityName)) + { + filterentity = entityaliases.FirstOrDefault(a => a.Key.Equals(cond.EntityName)).Value ?? cond.EntityName; + entityalias = "\"" + cond.EntityName + "\", "; + token += "_" + cond.EntityName; + } + token += "_" + cond.AttributeName; + if (cond.Values.Count > 0) + { + values = ", " + QueryExpressionCodeGenerator.GetConditionValues(cond.Values, token, gen.settings.FilterVariables); + if (cond.CompareColumns) + { + values = ", true" + values; + } + } + if (cond.Operator == ConditionOperator.Equal && cond.Values.Count == 1 && string.IsNullOrEmpty(entityalias)) + { + conditionscode.Add($"{gen.GetCodeAttribute(filterentity, cond.AttributeName)}{values}"); + } + else + { + conditionscode.Add($"{LineStart}.AddCondition({entityalias}{gen.GetCodeAttribute(filterentity, cond.AttributeName)}, ConditionOperator.{cond.Operator}{values});"); + } + } + return Indent + string.Join($",{CRLF}{Indent}", conditionscode); + } + + private string GetOrders(string entityname, DataCollection orders, string LineStart, bool root = false) + { + if (orders.Count == 0) + { + return string.Empty; + } + var code = new StringBuilder(); + if (comments) + { + code.AppendLine(); + code.AppendLine("// Add orders"); + } + LineStart += root ? ".AddOrder(" : ".Orders.Add(new OrderExpression("; + var LineEnd = root ? ");" : "));"; + foreach (var order in orders) + { + code.AppendLine(LineStart + gen.GetCodeAttribute(entityname, order.AttributeName) + ", OrderType." + order.OrderType.ToString() + LineEnd); + } + return code.ToString(); + } + } +} \ No newline at end of file diff --git a/FetchXmlBuilder/Converters/QExFluent.cs b/FetchXmlBuilder/Converters/QExFluent.cs new file mode 100644 index 00000000..e2a50f82 --- /dev/null +++ b/FetchXmlBuilder/Converters/QExFluent.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Cinteros.Xrm.FetchXmlBuilder.Converters +{ + internal class QExFluent + { + } +} diff --git a/FetchXmlBuilder/Converters/QExParse.cs b/FetchXmlBuilder/Converters/QExParse.cs new file mode 100644 index 00000000..4be9b955 --- /dev/null +++ b/FetchXmlBuilder/Converters/QExParse.cs @@ -0,0 +1,75 @@ +using Microsoft.CSharp; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Rappen.XTB.FetchXmlBuilder.Extensions; +using System; +using System.CodeDom.Compiler; +using System.Text; +using System.Text.RegularExpressions; + +namespace Cinteros.Xrm.FetchXmlBuilder.Converters +{ + internal static class QExParse + { + private delegate QueryExpression queryExpressionCompiler(); + + internal static string GetFetchXmlFromCSharpQueryExpression(string query, IOrganizationService organizationService) + { + CSharpCodeProvider provider = new CSharpCodeProvider(); + CompilerParameters parameters = new CompilerParameters(); + + parameters.ReferencedAssemblies.Add("Microsoft.Xrm.Sdk.dll"); + parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll"); + parameters.GenerateInMemory = true; + parameters.GenerateExecutable = false; + + CompilerResults compilerResults = provider.CompileAssemblyFromSource(parameters, GetQueryExpressionFromScript(query)); + + if (compilerResults.Errors.HasErrors) + { + StringBuilder sbuilder = new StringBuilder(); + foreach (CompilerError compilerError in compilerResults.Errors) + { + sbuilder.AppendLine($"Error ({compilerError.ErrorNumber}): {compilerError.ErrorText}"); + } + throw new InvalidOperationException(sbuilder.ToString()); + } + + QueryExpression queryExpression = + ((queryExpressionCompiler)Delegate.CreateDelegate(typeof(queryExpressionCompiler), + compilerResults.CompiledAssembly.GetType("DynamicContentGenerator.Generator"), "Generate"))(); + + return organizationService.QueryExpressionToFetchXml(queryExpression); + } + + private static string GetQueryExpressionFromScript(string query) + { + Regex varMatcher = new Regex(@"(var|QueryExpression)\W+([^\W]+)\W*=\W*new QueryExpression\W*\("); + Match match = varMatcher.Match(query); + + if (match.Success) + { + return $@" + using System; + using Microsoft.Xrm.Sdk; + using Microsoft.Xrm.Sdk.Query; + + namespace DynamicContentGenerator + {{ + public class Generator + {{ + public static QueryExpression Generate() + {{ + {query} + return {match.Groups[2].Value}; + }} + }} + }}"; + } + else + { + throw new Exception("Could not determine QueryExpression variable."); + } + } + } +} \ No newline at end of file diff --git a/FetchXmlBuilder/Converters/QExVanilla.cs b/FetchXmlBuilder/Converters/QExVanilla.cs new file mode 100644 index 00000000..3e31d3f8 --- /dev/null +++ b/FetchXmlBuilder/Converters/QExVanilla.cs @@ -0,0 +1,199 @@ +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Metadata; +using Microsoft.Xrm.Sdk.Query; +using Rappen.XTB.FetchXmlBuilder.Converters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Cinteros.Xrm.FetchXmlBuilder.Converters +{ + internal class QExVanilla + { + private readonly QueryExpressionCodeGenerator gen; + private readonly bool comments; + private readonly string CRLF; + private readonly string Indent; + + internal QExVanilla(QueryExpressionCodeGenerator generator) + { + if (generator.settings.QExFlavor == QExFlavorEnum.EarlyBound) + { + throw new ArgumentOutOfRangeException("Flavor", "Early Bound is not possible for vanilla CRM SDK libraries."); + } + gen = generator ?? throw new ArgumentNullException(nameof(generator)); + comments = gen.settings.IncludeComments; + CRLF = QueryExpressionCodeGenerator.CRLF; + Indent = QueryExpressionCodeGenerator.Indent; + } + + internal string Generated => CreateCode(gen.qex); + + private string CreateCode(QueryExpression qex) + { + var code = new StringBuilder(); + var qename = gen.GetVarName("query"); + code.Append(GetQueryCodeStart(qename, qex)); + code.Append(GetColumns(qex.EntityName, qex.ColumnSet, qename + ".ColumnSet")); + code.Append(GetFilter(qex.EntityName, qex.Criteria, qename, "Criteria")); + code.Append(GetOrders(qex.EntityName, qex.Orders, qename, true)); + code.Append(GetLinkEntities(qex.LinkEntities, qename)); + var codestr = gen.ReplaceValueTokens(code.ToString()); + return codestr; + } + + private string GetQueryCodeStart(string qename, QueryExpression qex) + { + var querycode = string.Empty; + if (comments) + { + querycode += "// Instantiate QueryExpression " + qename + CRLF; + } + querycode += "var " + qename + " = new QueryExpression(" + gen.GetCodeEntity(qex.EntityName) + ")"; + + querycode += QueryExpressionCodeGenerator.GetQueryOptions(qex) + ";" + CRLF; + return querycode; + } + + private string GetColumns(string entity, ColumnSet columns, string LineStart) + { + var code = new StringBuilder(); + if (columns.AllColumns) + { + code.AppendLine(); + if (comments) + { + code.AppendLine("// Add all columns to " + LineStart); + } + code.AppendLine(LineStart + ".AllColumns = true;"); + } + else if (columns.Columns.Count > 0) + { + if (comments) + { + code.AppendLine(); + code.AppendLine("// Add columns to " + LineStart); + } + LineStart += ".AddColumn" + (columns.Columns.Count > 1 ? "s" : "") + "("; + var LineEnd = ");"; + var cols = QueryExpressionCodeGenerator.GetCodeParametersMaxWidth(120 - LineStart.Length, 1, columns.Columns.Select(c => gen.GetCodeAttribute(entity, c)).ToArray()); + code.AppendLine(LineStart + cols + LineEnd); + } + return code.ToString(); + } + + private string GetLinkEntities(DataCollection linkEntities, string LineStart) + { + if (linkEntities?.Count == 0) + { + return string.Empty; + } + var code = new StringBuilder(); + foreach (var link in linkEntities) + { + var linkname = gen.GetVarName(string.IsNullOrEmpty(link.EntityAlias) ? LineStart + "_" + link.LinkToEntityName : link.EntityAlias); + code.AppendLine(); + if (comments) + { + code.AppendLine("// Add link-entity " + linkname); + } + var join = link.JoinOperator == JoinOperator.Inner ? "" : "JoinOperator." + link.JoinOperator.ToString(); + var varstart = + link.LinkEntities.Count > 0 || + link.Columns.Columns.Count > 0 || + link.LinkCriteria.Conditions.Count > 0 || + link.Orders.Count > 0 ? $"var {linkname} = " : String.Empty; + var parms = QueryExpressionCodeGenerator.GetCodeParametersMaxWidth(120 - varstart.Length - LineStart.Length, 1, + gen.GetCodeEntity(link.LinkToEntityName), + gen.GetCodeAttribute(link.LinkFromEntityName, + link.LinkFromAttributeName), + gen.GetCodeAttribute(link.LinkToEntityName, link.LinkToAttributeName), + join); + code.AppendLine($"{varstart}{LineStart}.AddLink({parms});"); + if (!string.IsNullOrWhiteSpace(link.EntityAlias)) + { + code.AppendLine(linkname + ".EntityAlias = \"" + link.EntityAlias + "\";"); + } + code.Append(GetColumns(link.LinkToEntityName, link.Columns, linkname + ".Columns")); + code.Append(GetFilter(link.LinkToEntityName, link.LinkCriteria, linkname, "LinkCriteria")); + code.Append(GetOrders(link.LinkToEntityName, link.Orders, linkname)); + code.Append(GetLinkEntities(link.LinkEntities, linkname)); + } + return code.ToString(); + } + + private string GetFilter(string entity, FilterExpression filterExpression, string parentName, string property) + { + var LineStart = parentName + (!string.IsNullOrEmpty(property) ? "." + property : ""); + var code = new StringBuilder(); + if (filterExpression.FilterOperator == LogicalOperator.Or || filterExpression.Conditions.Count > 0 || filterExpression.Filters.Count > 0) + { + if (comments) + { + code.AppendLine(); + code.AppendLine("// Add filter " + LineStart); + } + if (filterExpression.FilterOperator == LogicalOperator.Or) + { + code.AppendLine(LineStart + ".FilterOperator = LogicalOperator.Or;"); + } + foreach (var cond in filterExpression.Conditions) + { + var filterentity = entity; + var entityalias = ""; + var values = ""; + var token = LineStart.Replace(".", "_").Replace("_Criteria", "").Replace("_LinkCriteria", ""); + if (!string.IsNullOrWhiteSpace(cond.EntityName)) + { + filterentity = gen.entityaliases.FirstOrDefault(a => a.Key.Equals(cond.EntityName)).Value ?? cond.EntityName; + entityalias = "\"" + cond.EntityName + "\", "; + token += "_" + cond.EntityName; + } + token += "_" + cond.AttributeName; + if (cond.Values.Count > 0) + { + values = ", " + QueryExpressionCodeGenerator.GetConditionValues(cond.Values, token, gen.settings.FilterVariables); + + if (cond.CompareColumns) + { + values = ", true" + values; + } + } + code.AppendLine($"{LineStart}.AddCondition({entityalias}{gen.GetCodeAttribute(filterentity, cond.AttributeName)}, ConditionOperator.{cond.Operator}{values});"); + } + var i = 0; + foreach (var subfilter in filterExpression.Filters) + { + var filtername = gen.GetVarName(LineStart.Replace(".", "_") + "_" + i.ToString()); + code.AppendLine($"var {filtername} = new FilterExpression();"); + code.AppendLine($"{LineStart}.AddFilter({filtername});"); + code.Append(GetFilter(entity, subfilter, filtername, null)); + i++; + } + } + return code.ToString(); + } + + private string GetOrders(string entityname, DataCollection orders, string LineStart, bool root = false) + { + if (orders.Count == 0) + { + return string.Empty; + } + var code = new StringBuilder(); + if (comments) + { + code.AppendLine(); + code.AppendLine("// Add orders"); + } + LineStart += root ? ".AddOrder(" : ".Orders.Add(new OrderExpression("; + var LineEnd = root ? ");" : "));"; + foreach (var order in orders) + { + code.AppendLine(LineStart + gen.GetCodeAttribute(entityname, order.AttributeName) + ", OrderType." + order.OrderType.ToString() + LineEnd); + } + return code.ToString(); + } + } +} \ No newline at end of file diff --git a/FetchXmlBuilder/Converters/QueryExpressionCodeGenerator.cs b/FetchXmlBuilder/Converters/QueryExpressionCodeGenerator.cs index 5b661253..d68f7195 100644 --- a/FetchXmlBuilder/Converters/QueryExpressionCodeGenerator.cs +++ b/FetchXmlBuilder/Converters/QueryExpressionCodeGenerator.cs @@ -1,145 +1,71 @@ -using Microsoft.CSharp; +using Cinteros.Xrm.FetchXmlBuilder.Converters; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Metadata; using Microsoft.Xrm.Sdk.Query; -using Rappen.XTB.FetchXmlBuilder.Extensions; using Rappen.XTB.FetchXmlBuilder.Settings; using System; -using System.CodeDom.Compiler; using System.Collections.Generic; +using System.Drawing; using System.Linq; +using System.Reflection.Emit; using System.Text; -using System.Text.RegularExpressions; +using System.Xml.Linq; namespace Rappen.XTB.FetchXmlBuilder.Converters { public class QueryExpressionCodeGenerator { - private const string CRLF = "\r\n"; - private const string Indent = " "; private List varList; - private QueryExpression qex; - private List metas; - private Dictionary entityaliases; - private FXBSettings settings; - - private delegate QueryExpression queryExpressionCompiler(); + internal static string CRLF = "\r\n"; + internal static string Indent = " "; + internal QueryExpression qex; + internal List metas; + internal CodeGenerators settings; + internal Dictionary entityaliases; public static string GetCSharpQueryExpression(QueryExpression QEx, List entities, FXBSettings settings) { - return new QueryExpressionCodeGenerator - { - metas = entities, - qex = QEx, - settings = settings - }.CreateCSharpQueryExpression(); - } - - public static string GetFetchXmlFromCSharpQueryExpression(string query, IOrganizationService organizationService) - { - CSharpCodeProvider provider = new CSharpCodeProvider(); - CompilerParameters parameters = new CompilerParameters(); - - parameters.ReferencedAssemblies.Add("Microsoft.Xrm.Sdk.dll"); - parameters.ReferencedAssemblies.Add("System.Runtime.Serialization.dll"); - parameters.GenerateInMemory = true; - parameters.GenerateExecutable = false; - - CompilerResults compilerResults = provider.CompileAssemblyFromSource(parameters, GetQueryExpressionFromScript(query)); - - if (compilerResults.Errors.HasErrors) - { - StringBuilder sbuilder = new StringBuilder(); - foreach (CompilerError compilerError in compilerResults.Errors) - { - sbuilder.AppendLine($"Error ({compilerError.ErrorNumber}): {compilerError.ErrorText}"); - } - throw new InvalidOperationException(sbuilder.ToString()); - } - - QueryExpression queryExpression = - ((queryExpressionCompiler)Delegate.CreateDelegate(typeof(queryExpressionCompiler), - compilerResults.CompiledAssembly.GetType("DynamicContentGenerator.Generator"), "Generate"))(); - - return organizationService.QueryExpressionToFetchXml(queryExpression); - } - - private string CreateCSharpQueryExpression() - { - varList = new List(); - entityaliases = new Dictionary(); - var code = new StringBuilder(); - var qename = GetVarName("query"); - code.Append(GetQueryCodeStart(qename)); - code.Append(GetQueryOptions()); - code.Append(GetQueryCodeEnd()); - code.Append(GetColumns(qex.EntityName, qex.ColumnSet, qename + ".ColumnSet")); - code.Append(GetFilter(qex.EntityName, qex.Criteria, qename, "Criteria")); - code.Append(GetOrders(qex.EntityName, qex.Orders, qename, true)); - code.Append(GetLinkEntities(qex.LinkEntities, qename)); - var codestr = ReplaceValueTokens(code.ToString()); - return codestr; - } - - private string GetQueryCodeStart(string qename) - { - var querycode = string.Empty; - if (settings.CodeGenerators.IncludeComments) + if (settings.CodeGenerators.QExFlavor == QExFlavorEnum.LCGconstants) { - querycode += "// Instantiate QueryExpression " + qename + CRLF; + throw new ArgumentOutOfRangeException("Flavor", "LCG is not yet implemented."); } - switch (settings.CodeGenerators.Style) + var codegenerator = new QueryExpressionCodeGenerator(QEx, entities, settings); + switch (settings.CodeGenerators.QExStyle) { - case CodeGenerationStyle.QueryExpressionFactory: - querycode += "var " + qename + " = QueryExpressionFactory.Create<" + GetCodeEntity(qex.EntityName) + ">("; - break; + case QExStyleEnum.QueryExpression: + return new QExVanilla(codegenerator).Generated; + + case QExStyleEnum.QueryExpressionFactory: + return new QExFactory(codegenerator).Generated; default: - querycode += "var " + qename + " = new QueryExpression(" + GetCodeEntity(qex.EntityName) + ")"; - break; + throw new NotImplementedException(); } - - return querycode; } - private string GetQueryCodeEnd() + private QueryExpressionCodeGenerator(QueryExpression QEx, List entities, FXBSettings fxbsettings) { - switch (settings.CodeGenerators.Style) - { - case CodeGenerationStyle.QueryExpressionFactory: - return ");" + CRLF; - default: - return ";" + CRLF; - } + varList = new List(); + entityaliases = new Dictionary(); + metas = entities; + qex = QEx; + settings = fxbsettings.CodeGenerators; + StoreLinkEntityAliases(qex.LinkEntities); } - private string GetQueryOptions() + private void StoreLinkEntityAliases(DataCollection linkEntities) { - var queryoptions = new List(); - if (qex.NoLock) - { - queryoptions.Add("NoLock = true"); - } - if (qex.Distinct) - { - queryoptions.Add("Distinct = true"); - } - if (qex.TopCount != null) - { - queryoptions.Add("TopCount = " + qex.TopCount.ToString()); - } - if (!string.IsNullOrWhiteSpace(qex.PageInfo?.PagingCookie)) - { - queryoptions.Add($"PageInfo = new PagingInfo{CRLF}{Indent}{{{CRLF}{Indent}{Indent}PageNumber = {qex.PageInfo.PageNumber},{CRLF}{Indent}{Indent}PagingCookie = \"{qex.PageInfo.PagingCookie}\"{CRLF}{Indent}}}"); - } - if (queryoptions.Count > 0) + foreach (var link in linkEntities) { - return CRLF + "{" + GetCodeParametersMaxWidth(0, queryoptions.ToArray()) + "}"; + if (!string.IsNullOrWhiteSpace(link.EntityAlias)) + { + entityaliases.Add(link.EntityAlias, link.LinkToEntityName); + } + StoreLinkEntityAliases(link.LinkEntities); } - return string.Empty; } - private string GetVarName(string requestedname) + internal string GetVarName(string requestedname) { var result = requestedname; if (varList.Contains(result)) @@ -155,158 +81,14 @@ private string GetVarName(string requestedname) return result; } - private string GetColumns(string entity, ColumnSet columns, string LineStart) - { - var code = new StringBuilder(); - if (columns.AllColumns) - { - code.AppendLine(); - if (settings.CodeGenerators.IncludeComments) - { - code.AppendLine("// Add all columns to " + LineStart); - } - code.AppendLine(LineStart + ".AllColumns = true;"); - } - else if (columns.Columns.Count > 0) - { - if (settings.CodeGenerators.IncludeComments) - { - code.AppendLine(); - code.AppendLine("// Add columns to " + LineStart); - } - LineStart = - settings.CodeGenerators.Style == CodeGenerationStyle.QueryExpressionFactory ? - GetCodeEntityPrefix(entity) + " => new { " : - LineStart + ".AddColumn" + (columns.Columns.Count > 1 ? "s" : "") + "("; - var LineEnd = settings.CodeGenerators.Style == CodeGenerationStyle.QueryExpressionFactory ? " }" : ");"; - var cols = GetCodeParametersMaxWidth(120 - LineStart.Length, columns.Columns.Select(c => GetCodeAttribute(entity, c)).ToArray()); - code.AppendLine(LineStart + cols + LineEnd); - } - return code.ToString(); - } - - private string GetLinkEntities(DataCollection linkEntities, string LineStart) - { - if (linkEntities?.Count == 0) - { - return string.Empty; - } - var code = new StringBuilder(); - foreach (var link in linkEntities) - { - var linkname = GetVarName(string.IsNullOrEmpty(link.EntityAlias) ? LineStart + "_" + link.LinkToEntityName : link.EntityAlias); - code.AppendLine(); - if (settings.CodeGenerators.IncludeComments) - { - code.AppendLine("// Add link-entity " + linkname); - } - var join = link.JoinOperator == JoinOperator.Inner ? "" : "JoinOperator." + link.JoinOperator.ToString(); - var varstart = - link.LinkEntities.Count > 0 || - link.Columns.Columns.Count > 0 || - link.LinkCriteria.Conditions.Count > 0 || - link.Orders.Count > 0 ? $"var {linkname} = " : String.Empty; - var parms = GetCodeParametersMaxWidth(120 - varstart.Length - LineStart.Length, - GetCodeEntity(link.LinkToEntityName), - GetCodeAttribute(link.LinkFromEntityName, - link.LinkFromAttributeName), - GetCodeAttribute(link.LinkToEntityName, link.LinkToAttributeName), - join); - code.AppendLine($"{varstart}{LineStart}.AddLink({parms});"); - if (!string.IsNullOrWhiteSpace(link.EntityAlias)) - { - entityaliases.Add(link.EntityAlias, link.LinkToEntityName); - code.AppendLine(linkname + ".EntityAlias = \"" + link.EntityAlias + "\";"); - } - code.Append(GetColumns(link.LinkToEntityName, link.Columns, linkname + ".Columns")); - code.Append(GetFilter(link.LinkToEntityName, link.LinkCriteria, linkname, "LinkCriteria")); - code.Append(GetOrders(link.LinkToEntityName, link.Orders, linkname)); - code.Append(GetLinkEntities(link.LinkEntities, linkname)); - } - return code.ToString(); - } - - private string GetFilter(string entity, FilterExpression filterExpression, string parentName, string property) - { - var LineStart = parentName + (!string.IsNullOrEmpty(property) ? "." + property : ""); - var code = new StringBuilder(); - if (filterExpression.FilterOperator == LogicalOperator.Or || filterExpression.Conditions.Count > 0 || filterExpression.Filters.Count > 0) - { - if (settings.CodeGenerators.IncludeComments) - { - code.AppendLine(); - code.AppendLine("// Add filter " + LineStart); - } - if (filterExpression.FilterOperator == LogicalOperator.Or) - { - code.AppendLine(LineStart + ".FilterOperator = LogicalOperator.Or;"); - } - foreach (var cond in filterExpression.Conditions) - { - var filterentity = entity; - var entityalias = ""; - var values = ""; - var token = LineStart.Replace(".", "_").Replace("_Criteria", "").Replace("_LinkCriteria", ""); - if (!string.IsNullOrWhiteSpace(cond.EntityName)) - { - filterentity = entityaliases.FirstOrDefault(a => a.Key.Equals(cond.EntityName)).Value ?? cond.EntityName; - entityalias = "\"" + cond.EntityName + "\", "; - token += "_" + cond.EntityName; - } - token += "_" + cond.AttributeName; - if (cond.Values.Count > 0) - { - values = ", " + GetConditionValues(cond.Values, token); - - if (cond.CompareColumns) - { - values = ", true" + values; - } - } - code.AppendLine($"{LineStart}.AddCondition({entityalias}{GetCodeAttribute(filterentity, cond.AttributeName)}, ConditionOperator.{cond.Operator}{values});"); - } - var i = 0; - foreach (var subfilter in filterExpression.Filters) - { - var filtername = GetVarName(LineStart.Replace(".", "_") + "_" + i.ToString()); - code.AppendLine($"var {filtername} = new FilterExpression();"); - code.AppendLine($"{LineStart}.AddFilter({filtername});"); - code.Append(GetFilter(entity, subfilter, filtername, null)); - i++; - } - } - return code.ToString(); - } - - private string GetOrders(string entityname, DataCollection orders, string LineStart, bool root = false) - { - if (orders.Count == 0) - { - return string.Empty; - } - var code = new StringBuilder(); - if (settings.CodeGenerators.IncludeComments) - { - code.AppendLine(); - code.AppendLine("// Add orders"); - } - LineStart += root ? ".AddOrder(" : ".Orders.Add(new OrderExpression("; - var LineEnd = root ? ");" : "));"; - foreach (var order in orders) - { - code.AppendLine(LineStart + GetCodeAttribute(entityname, order.AttributeName) + ", OrderType." + order.OrderType.ToString() + LineEnd); - } - return code.ToString(); - } - - private string ReplaceValueTokens(string code) + internal string ReplaceValueTokens(string code) { if (!code.Contains("<<<")) { return code; } var variables = new StringBuilder(); - if (settings.CodeGenerators.IncludeComments) + if (settings.IncludeComments) { variables.AppendLine("// Set Condition Values"); } @@ -329,59 +111,36 @@ private string ReplaceValueTokens(string code) return code; } - private string GetCodeEntity(string entityname) + internal static string GetQueryOptions(QueryExpression qex) { - if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity) + var queryoptions = new List(); + if (qex.NoLock) { - switch (settings.CodeGenerators.Style) - { - case CodeGenerationStyle.EarlyBoundEBG: - return entity.SchemaName + "." + settings.CodeGenerators.EBG_EntityLogicalNames; - - case CodeGenerationStyle.QueryExpressionFactory: - return entity.SchemaName; - } + queryoptions.Add("NoLock = true"); } - return "\"" + entityname + "\""; - } - - private string GetCodeEntityPrefix(string entityname) - { - if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity) + if (qex.Distinct) { - switch (settings.CodeGenerators.Style) - { - case CodeGenerationStyle.EarlyBoundEBG: - case CodeGenerationStyle.QueryExpressionFactory: - return entity.DisplayName.UserLocalizedLabel.Label.Substring(0, 1).ToLowerInvariant(); - } + queryoptions.Add("Distinct = true"); } - return entityname.Substring(0, 1).ToLowerInvariant(); - } - - private string GetCodeAttribute(string entityname, string attributename) - { - if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity && - entity.Attributes.FirstOrDefault(a => a.LogicalName.Equals(attributename)) is AttributeMetadata attribute) + if (qex.TopCount != null) { - switch (settings.CodeGenerators.Style) - { - case CodeGenerationStyle.EarlyBoundEBG: - return entity.SchemaName + "." + settings.CodeGenerators.EBG_AttributeLogicalNameClass + attribute.SchemaName; - - case CodeGenerationStyle.QueryExpressionFactory: - return GetCodeEntityPrefix(entityname) + "." + attribute.SchemaName; - } + queryoptions.Add("TopCount = " + qex.TopCount.ToString()); } - return "\"" + attributename + "\""; + if (!string.IsNullOrWhiteSpace(qex.PageInfo?.PagingCookie)) + { + queryoptions.Add($"PageInfo = new PagingInfo{CRLF}{Indent}{{{CRLF}{Indent}{Indent}PageNumber = {qex.PageInfo.PageNumber},{CRLF}{Indent}{Indent}PagingCookie = \"{qex.PageInfo.PagingCookie}\"{CRLF}{Indent}}}"); + } + var options = queryoptions.Count > 0 ? CRLF + "{" + QueryExpressionCodeGenerator.GetCodeParametersMaxWidth(0, 1, queryoptions.ToArray()) + "}" : ""; + return options; } - private static string GetCodeParametersMaxWidth(int maxwidth, params string[] parameters) + internal static string GetCodeParametersMaxWidth(int maxwidth, int indents, params string[] parameters) { + var currentindent = string.Concat(Enumerable.Repeat(Indent, indents)); var result = string.Join("`´", parameters.Where(p => !string.IsNullOrWhiteSpace(p))); if (result.Length > maxwidth) { - result = CRLF + Indent + result.Replace("`´", $",{CRLF}{Indent}") + CRLF; + result = CRLF + currentindent + result.Replace("`´", $",{CRLF}{currentindent}") + CRLF; } else { @@ -390,7 +149,7 @@ private static string GetCodeParametersMaxWidth(int maxwidth, params string[] pa return result; } - private static string GetConditionValues(DataCollection values, string token) + internal static string GetConditionValues(DataCollection values, string token, bool createvariables) { var strings = new List(); var i = 1; @@ -409,46 +168,120 @@ private static string GetConditionValues(DataCollection values, string t { valuestr = value.ToString(); } - if (values.Count == 1) + if (createvariables) { - strings.Add($"<<<{token}|{valuestr}>>>"); + if (values.Count == 1) + { + strings.Add($"<<<{token}|{valuestr}>>>"); + } + else + { + strings.Add($"<<<{token}_{i++}|{valuestr}>>>"); + } } else { - strings.Add($"<<<{token}_{i++}|{valuestr}>>>"); + strings.Add(valuestr); } } return string.Join(", ", strings); } - private static string GetQueryExpressionFromScript(string query) + internal string GetCodeEntity(string entityname) { - Regex varMatcher = new Regex(@"(var|QueryExpression)\W+([^\W]+)\W*=\W*new QueryExpression\W*\("); - Match match = varMatcher.Match(query); - - if (match.Success) + if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity) { - return $@" - using System; - using Microsoft.Xrm.Sdk; - using Microsoft.Xrm.Sdk.Query; + if (settings.QExFlavor == QExFlavorEnum.EBGconstants) + { + return entity.SchemaName + "." + settings.EBG_EntityLogicalNames; + } + else if (settings.QExFlavor == QExFlavorEnum.EarlyBound) + { + return entity.SchemaName; + } + } + return "\"" + entityname + "\""; + } - namespace DynamicContentGenerator - {{ - public class Generator - {{ - public static QueryExpression Generate() - {{ - {query} - return {match.Groups[2].Value}; - }} - }} - }}"; + internal string GetCodeEntityPrefix(string entityname) + { + if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity) + { + return entity.DisplayName.UserLocalizedLabel.Label.Substring(0, 1).ToLowerInvariant(); } - else + return entityname.Substring(0, 1).ToLowerInvariant(); + } + + internal string GetCodeAttribute(string entityname, string attributename) + { + if (metas.FirstOrDefault(e => e.LogicalName.Equals(entityname)) is EntityMetadata entity && + entity.Attributes.FirstOrDefault(a => a.LogicalName.Equals(attributename)) is AttributeMetadata attribute) { - throw new Exception("Could not determine QueryExpression variable."); + if (settings.QExFlavor == QExFlavorEnum.EBGconstants) + { + return entity.SchemaName + "." + settings.EBG_AttributeLogicalNameClass + attribute.SchemaName; + } + else if (settings.QExFlavor == QExFlavorEnum.EarlyBound) + { + return GetCodeEntityPrefix(entityname) + "." + attribute.SchemaName; + } } + return "\"" + attributename + "\""; + } + } + + public class QExStyle + { + public QExStyleEnum Tag; + public string Creator; + public string HelpUrl; + public List Flavors; + + public override string ToString() => $"{Tag} ({Creator})"; + + internal static object[] GetComboBoxItems() + { + return new object[] + { + new QExStyle { Tag = QExStyleEnum.QueryExpression, Creator = "Microsoft SDK" }, + new QExStyle { Tag = QExStyleEnum.FluentQueryExpression, Creator = "MscrmTools" }, + new QExStyle { Tag = QExStyleEnum.QueryExpressionFactory, Creator = "DLaB" } + }; } } + + public class QExFlavor + { + public string Name; + public QExFlavorEnum Tag; + public string HelpUrl; + + public override string ToString() => $"{Name}"; + + internal static object[] GetComboBoxItems() + { + return new object[] + { + new QExFlavor { Name = "Late Bound strings", Tag = QExFlavorEnum.LateBound }, + new QExFlavor { Name = "Late Bound EBG constants", Tag = QExFlavorEnum.EBGconstants }, + new QExFlavor { Name = "Late Bound LCG constants", Tag = QExFlavorEnum.LCGconstants }, + new QExFlavor { Name = "Early Bound", Tag = QExFlavorEnum.EarlyBound } + }; + } + } + + public enum QExStyleEnum + { + QueryExpression, + FluentQueryExpression, + QueryExpressionFactory + } + + public enum QExFlavorEnum + { + LateBound, + EBGconstants, + LCGconstants, + EarlyBound + } } \ No newline at end of file diff --git a/FetchXmlBuilder/FXBQueries.cs b/FetchXmlBuilder/FXBQueries.cs index 1bfa36f9..cc977741 100644 --- a/FetchXmlBuilder/FXBQueries.cs +++ b/FetchXmlBuilder/FXBQueries.cs @@ -1,6 +1,8 @@ -using Microsoft.Crm.Sdk.Messages; +using Cinteros.Xrm.FetchXmlBuilder.Converters; +using Microsoft.Crm.Sdk.Messages; using Microsoft.Xrm.Sdk; using Microsoft.Xrm.Sdk.Query; +using Rappen.XRM.Helpers.Extensions; using Rappen.XRM.Helpers.Serialization; using Rappen.XTB.FetchXmlBuilder.AppCode; using Rappen.XTB.FetchXmlBuilder.Converters; @@ -19,13 +21,13 @@ public partial class FetchXmlBuilder { private string attributesChecksum = ""; - internal EntityCollection RetrieveMultiple(QueryBase query) + internal EntityCollection RetrieveMultiple(QueryBase query, bool allrecords = true) { EntityCollection result = null; var start = DateTime.Now; try { - result = Service.RetrieveMultiple(query); + result = allrecords ? Service.RetrieveMultipleAll(query) : Service.RetrieveMultiple(query); } finally { @@ -184,7 +186,7 @@ internal void QueryExpressionToFetchXml(string query) (eventargs) => { var start = DateTime.Now; - string fetchXml = QueryExpressionCodeGenerator.GetFetchXmlFromCSharpQueryExpression(query, Service); + string fetchXml = QExParse.GetFetchXmlFromCSharpQueryExpression(query, Service); var stop = DateTime.Now; var duration = stop - start; LogUse("QueryExpressionToFetchXml", false, null, duration.TotalMilliseconds); @@ -279,7 +281,7 @@ private void RetrieveMultiple(string fetch) eventargs.Cancel = true; break; } - tmpResult = RetrieveMultiple(query); + tmpResult = RetrieveMultiple(query, false); if (resultCollection == null) { resultCollection = tmpResult; diff --git a/FetchXmlBuilder/FetchXmlBuilder.Designer.cs b/FetchXmlBuilder/FetchXmlBuilder.Designer.cs index e0527eef..935f6685 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.Designer.cs +++ b/FetchXmlBuilder/FetchXmlBuilder.Designer.cs @@ -574,6 +574,8 @@ private void InitializeComponent() this.tsmiShowQueryExpression.Size = new System.Drawing.Size(289, 22); this.tsmiShowQueryExpression.Text = "QueryExpression"; this.tsmiShowQueryExpression.Click += new System.EventHandler(this.tsmiShowQueryExpression_Click); + this.tsmiShowQueryExpression.ShortcutKeys = ((System.Windows.Forms.Keys)(((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Alt) + | System.Windows.Forms.Keys.C))); // // tsmiShowFetchXMLcs // diff --git a/FetchXmlBuilder/FetchXmlBuilder.cs b/FetchXmlBuilder/FetchXmlBuilder.cs index 526d0fe0..cd15ef4d 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.cs +++ b/FetchXmlBuilder/FetchXmlBuilder.cs @@ -68,7 +68,7 @@ public FetchXmlBuilder() //AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(Error_UnhandledException); tslAbout.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString() + " by Jonas Rapp"; - + ai = new AppInsights(aiEndpoint, aiKey, Assembly.GetExecutingAssembly(), "FetchXML Builder"); var theme = new VS2015LightTheme(); dockContainer.Theme = theme; @@ -397,11 +397,11 @@ private string GetQueryExpressionCode() } catch (FetchIsAggregateException ex) { - code = "This FetchXML is not possible to convert to QueryExpression in the current version of the SDK.\n\n" + ex.Message; + code = $"/*\nThis FetchXML is not possible to convert to QueryExpression in the current version of the SDK.\n\n{ex.Message}+n*/"; } catch (Exception ex) { - code = "Failed to generate C# QueryExpression code.\n\n" + ex.Message; + code = $"/*\nFailed to generate C# {settings.CodeGenerators.QExStyle} with {settings.CodeGenerators.QExFlavor} code.\n\n{ex.Message}\n*/"; } return code; } diff --git a/FetchXmlBuilder/FetchXmlBuilder.csproj b/FetchXmlBuilder/FetchXmlBuilder.csproj index 33cc5270..3a9f167c 100644 --- a/FetchXmlBuilder/FetchXmlBuilder.csproj +++ b/FetchXmlBuilder/FetchXmlBuilder.csproj @@ -228,6 +228,10 @@ + + + +