Skip to content

Commit

Permalink
Major changes for QueryExpression
Browse files Browse the repository at this point in the history
  • Loading branch information
rappen committed Dec 6, 2022
1 parent 7bf11b8 commit f4509d3
Show file tree
Hide file tree
Showing 10 changed files with 726 additions and 329 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,4 @@ ModelManifest.xml
/tmp
/.vs/config/applicationhost.config
/.vs
/QEx samples
269 changes: 269 additions & 0 deletions FetchXmlBuilder/Converters/QExFactory.cs
Original file line number Diff line number Diff line change
@@ -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<string, string> 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<string, string>();
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<LinkEntity> 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<FilterExpression> filters, string parentName, string property)
{
if (filters?.Any() != true)
{
return string.Empty;
}

var filterscode = new List<string>();
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<ConditionExpression> conditions, string LineStart)
{
if (conditions?.Any() != true)
{
return string.Empty;
}
var conditionscode = new List<string>();
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<OrderExpression> 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();
}
}
}
12 changes: 12 additions & 0 deletions FetchXmlBuilder/Converters/QExFluent.cs
Original file line number Diff line number Diff line change
@@ -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
{
}
}
75 changes: 75 additions & 0 deletions FetchXmlBuilder/Converters/QExParse.cs
Original file line number Diff line number Diff line change
@@ -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.");
}
}
}
}
Loading

0 comments on commit f4509d3

Please sign in to comment.