diff --git a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs index 334cd625..b07defcf 100644 --- a/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs +++ b/MarkMpn.Sql4Cds.Engine.Tests/ExecutionPlanTests.cs @@ -2210,6 +2210,134 @@ public void ExistsFilterCorrelated() "); } + [TestMethod] + public void ExistsFilterCorrelatedWithAny() + { + using (_localDataSource.EnableJoinOperator(JoinOperator.Any)) + { + var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this); + + var query = @" + SELECT + accountid, + name + FROM + account + WHERE + EXISTS (SELECT * FROM contact WHERE parentcustomerid = accountid) OR + name = 'Data8'"; + + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + + var select = AssertNode(plans[0]); + var fetch = AssertNode(select.Source); + AssertFetchXml(fetch, @" + + + + + + + + + + + + "); + } + } + + [TestMethod] + public void ExistsFilterCorrelatedWithAnyParentAndChildAndAdditionalFilter() + { + using (_localDataSource.EnableJoinOperator(JoinOperator.Any)) + { + var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this); + + var query = @" + SELECT + accountid, + name + FROM + account + WHERE + EXISTS (SELECT * FROM contact WHERE parentcustomerid = accountid AND firstname = 'Mark') AND + EXISTS (SELECT * FROM contact WHERE primarycontactid = contactid AND lastname = 'Carrington') AND + name = 'Data8'"; + + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + + var select = AssertNode(plans[0]); + var fetch = AssertNode(select.Source); + AssertFetchXml(fetch, @" + + + + + + + + + + + + + + + + + + + "); + } + } + + [TestMethod] + public void NotExistsFilterCorrelatedOnLinkEntity() + { + using (_localDataSource.EnableJoinOperator(JoinOperator.NotAny)) + { + var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this); + + var query = @" + SELECT + accountid, + name + FROM + account + INNER JOIN contact ON account.primarycontactid = contact.contactid + WHERE + NOT EXISTS (SELECT * FROM account WHERE accountid = contact.parentcustomerid AND name = 'Data8')"; + + var plans = planBuilder.Build(query, null, out _); + + Assert.AreEqual(1, plans.Length); + + var select = AssertNode(plans[0]); + var fetch = AssertNode(select.Source); + AssertFetchXml(fetch, @" + + + + + + + + + + + + + + + "); + } + } + [TestMethod] public void NotExistsFilterCorrelated() { @@ -3058,7 +3186,7 @@ public void FoldFilterWithInClauseOr() [TestMethod] public void FoldFilterWithInClauseWithoutPrimaryKey() { - using (_localDataSource.EnableJoinOperator(JoinOperator.Any)) + using (_localDataSource.EnableJoinOperator(JoinOperator.In)) { var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this); @@ -3074,7 +3202,7 @@ public void FoldFilterWithInClauseWithoutPrimaryKey() - + @@ -3121,7 +3249,7 @@ public void FoldNotInToLeftOuterJoin() [TestMethod] public void FoldFilterWithInClauseOnLinkEntityWithoutPrimaryKey() { - using (_localDataSource.EnableJoinOperator(JoinOperator.Any)) + using (_localDataSource.EnableJoinOperator(JoinOperator.In)) { var planBuilder = new ExecutionPlanBuilder(_localDataSources.Values, this); @@ -3142,7 +3270,7 @@ public void FoldFilterWithInClauseOnLinkEntityWithoutPrimaryKey() - + @@ -3171,7 +3299,7 @@ public void FoldFilterWithExistsClauseWithoutPrimaryKey() - + @@ -6738,7 +6866,7 @@ public void DoNotUseCustomPagingForInJoin() { // https://github.com/MarkMpn/Sql4Cds/issues/366 - using (_dataSource.EnableJoinOperator(JoinOperator.Any)) + using (_dataSource.EnableJoinOperator(JoinOperator.In)) { var planBuilder = new ExecutionPlanBuilder(_dataSources.Values, new OptionsWrapper(this) { PrimaryDataSource = "uat" }); @@ -6755,7 +6883,7 @@ WHERE contactid IN (SELECT DISTINCT primarycontactid FROM account WHERE name = - + diff --git a/MarkMpn.Sql4Cds.Engine/DataSource.cs b/MarkMpn.Sql4Cds.Engine/DataSource.cs index 9434c3a0..bd546a85 100644 --- a/MarkMpn.Sql4Cds.Engine/DataSource.cs +++ b/MarkMpn.Sql4Cds.Engine/DataSource.cs @@ -24,7 +24,14 @@ public class DataSource /// Creates a new using default values based on an existing connection. /// /// The that provides the connection to the instance - public DataSource(IOrganizationService org) + public DataSource(IOrganizationService org) : this(org, null, null, null) + { + Metadata = new AttributeMetadataCache(org); + TableSizeCache = new TableSizeCache(org, Metadata); + MessageCache = new MessageCache(org, Metadata); + } + + public DataSource(IOrganizationService org, IAttributeMetadataCache metadata, ITableSizeCache tableSize, IMessageCache messages) { string name = null; Version version = null; @@ -42,7 +49,7 @@ public DataSource(IOrganizationService org) version = svc.ConnectedOrgVersion; } #endif - + if (name == null) { var orgDetails = org.RetrieveMultiple(new QueryExpression("organization") { ColumnSet = new ColumnSet("name") }).Entities[0]; @@ -56,10 +63,10 @@ public DataSource(IOrganizationService org) } Connection = org; - Metadata = new AttributeMetadataCache(org); + Metadata = metadata; Name = name; - TableSizeCache = new TableSizeCache(org, Metadata); - MessageCache = new MessageCache(org, Metadata); + TableSizeCache = tableSize; + MessageCache = messages; var joinOperators = new List { @@ -70,8 +77,12 @@ public DataSource(IOrganizationService org) if (version >= new Version("9.1.0.17461")) { // First documented in SDK Version 9.0.2.25: Updated for 9.1.0.17461 CDS release - joinOperators.Add(JoinOperator.Any); + joinOperators.Add(JoinOperator.In); joinOperators.Add(JoinOperator.Exists); + joinOperators.Add(JoinOperator.Any); + joinOperators.Add(JoinOperator.NotAny); + joinOperators.Add(JoinOperator.All); + joinOperators.Add(JoinOperator.NotAll); } JoinOperatorsAvailable = joinOperators; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDataNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDataNode.cs index 37026f6f..0da6194a 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDataNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/BaseDataNode.cs @@ -23,6 +23,27 @@ namespace MarkMpn.Sql4Cds.Engine.ExecutionPlan /// abstract class BaseDataNode : BaseNode, IDataExecutionPlanNodeInternal { + /// + /// Holds data about a subquery filter (IN, EXISTS) that is needed to process the multi-step conversion + /// + protected class ConvertedSubquery + { + /// + /// The join that is used to process the subquery + /// + public BaseJoinNode JoinNode { get; set; } + + /// + /// The FetchXML equivalent of the subquery + /// + public FetchLinkEntityType Condition { get; set; } + + /// + /// The link entity to add the to + /// + public FetchLinkEntityType LinkEntity { get; set; } + } + private int _executionCount; private readonly Timer _timer = new Timer(); private TimeSpan _additionalDuration; @@ -204,9 +225,9 @@ public void MergeStatsFrom(BaseDataNode other) /// The child items of the root entity in the FetchXML query /// The FetchXML version of the that is generated by this method /// true if the can be translated to FetchXML, or false otherwise - protected bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, out filter filter) + protected bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, Dictionary subqueryExpressions, HashSet replacedSubqueryExpression, out filter filter) { - if (!TranslateFetchXMLCriteria(context, dataSource, criteria, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out var condition, out filter)) + if (!TranslateFetchXMLCriteria(context, dataSource, criteria, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpression, out var condition, out filter)) return false; if (condition != null) @@ -230,16 +251,34 @@ protected bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSou /// The FetchXML version of the that is generated by this method when it covers multiple conditions /// The FetchXML version of the that is generated by this method when it is for a single condition only /// true if the can be translated to FetchXML, or false otherwise - private bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, out condition condition, out filter filter) + private bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, Dictionary subqueryExpressions, HashSet replacedSubqueryExpression, out condition condition, out filter filter) { condition = null; filter = null; + if (criteria == null) + return false; + + if (subqueryExpressions != null && subqueryExpressions.TryGetValue(criteria, out var subqueryExpression)) + { + if (replacedSubqueryExpression != null) + replacedSubqueryExpression.Add(criteria); + + filter = new filter + { + Items = new[] + { + (object) subqueryExpression.Condition + } + }; + return true; + } + if (criteria is BooleanBinaryExpression binary) { - if (!TranslateFetchXMLCriteria(context, dataSource, binary.FirstExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out var lhsCondition, out var lhsFilter)) + if (!TranslateFetchXMLCriteria(context, dataSource, binary.FirstExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpression, out var lhsCondition, out var lhsFilter)) return false; - if (!TranslateFetchXMLCriteria(context, dataSource, binary.SecondExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out var rhsCondition, out var rhsFilter)) + if (!TranslateFetchXMLCriteria(context, dataSource, binary.SecondExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpression, out var rhsCondition, out var rhsFilter)) return false; filter = new filter @@ -256,7 +295,7 @@ private bool TranslateFetchXMLCriteria(NodeCompilationContext context, DataSourc if (criteria is BooleanParenthesisExpression paren) { - return TranslateFetchXMLCriteria(context, dataSource, paren.Expression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out condition, out filter); + return TranslateFetchXMLCriteria(context, dataSource, paren.Expression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpression, out condition, out filter); } if (criteria is DistinctPredicate distinct) diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs index c9fc9fa4..72203fa7 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FetchXmlScan.cs @@ -580,6 +580,18 @@ public void RemoveSorts() } } + public void RemoveAttributes() + { + // Remove any existing sorts + if (Entity.Items != null) + { + Entity.Items = Entity.Items.Where(i => !(i is FetchAttributeType) && !(i is allattributes)).ToArray(); + + foreach (var linkEntity in Entity.GetLinkEntities().Where(le => le.Items != null)) + linkEntity.Items = linkEntity.Items.Where(i => !(i is FetchAttributeType) && !(i is allattributes)).ToArray(); + } + } + private void OnRetrievedEntity(Entity entity, INodeSchema schema, IQueryExecutionOptions options, DataSource dataSource) { // Expose any formatted values for OptionSetValue and EntityReference values @@ -1159,7 +1171,7 @@ private void AddSchemaAttributes(NodeCompilationContext context, DataSource data foreach (var linkEntity in items.OfType()) { - if (linkEntity.SemiJoin) + if (linkEntity.SemiJoin || linkEntity.linktype == "in" || linkEntity.linktype == "exists") continue; if (primaryKey != null) @@ -1519,7 +1531,10 @@ private void MoveFiltersToLinkEntities() { // If we've got AND-ed conditions that have an entityname that refers to an inner-joined link entity, move // the condition to that link entity - var innerLinkEntities = Entity.GetLinkEntities(innerOnly: true).ToDictionary(le => le.alias, StringComparer.OrdinalIgnoreCase); + var innerLinkEntities = Entity + .GetLinkEntities(innerOnly: true) + .Where(le => le.alias != null) + .ToDictionary(le => le.alias, StringComparer.OrdinalIgnoreCase); Entity.Items = MoveFiltersToLinkEntities(innerLinkEntities, Entity.Items); Entity.Items = MoveConditionsToLinkEntities(innerLinkEntities, Entity.Items); @@ -1695,8 +1710,8 @@ private void MergeSingleConditionFilters(filter filter) { var singleConditionFilters = filter.Items .OfType() - .Where(f => f.Items != null && f.Items.Length == 1 && f.Items.OfType().Count() == 1) - .ToDictionary(f => f, f => (condition)f.Items[0]); + .Where(f => f.Items != null && f.Items.Length == 1 && f.Items.Where(x => !(x is filter)).Count() == 1) + .ToDictionary(f => f, f => f.Items[0]); for (var i = 0; i < filter.Items.Length; i++) { diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs index 0d43009e..b7a1b94c 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FilterNode.cs @@ -152,10 +152,10 @@ public override IDataExecutionPlanNodeInternal FoldQuery(NodeCompilationContext foldedFilters |= FoldConsecutiveFilters(); foldedFilters |= FoldNestedLoopFiltersToJoins(context, hints); - foldedFilters |= FoldInExistsToFetchXml(context, hints, out var addedLinks); + foldedFilters |= FoldInExistsToFetchXml(context, hints, out var addedLinks, out var subqueryConditions); foldedFilters |= FoldTableSpoolToIndexSpool(context, hints); foldedFilters |= ExpandFiltersOnColumnComparisons(context); - foldedFilters |= FoldFiltersToDataSources(context, hints); + foldedFilters |= FoldFiltersToDataSources(context, hints, subqueryConditions); foreach (var addedLink in addedLinks) { @@ -717,7 +717,7 @@ private void Swap(ref T first, ref T second) second = temp; } - private bool FoldInExistsToFetchXml(NodeCompilationContext context, IList hints, out Dictionary addedLinks) + private bool FoldInExistsToFetchXml(NodeCompilationContext context, IList hints, out Dictionary addedLinks, out Dictionary subqueryConditions) { var foldedFilters = false; @@ -747,6 +747,8 @@ private bool FoldInExistsToFetchXml(NodeCompilationContext context, IList(); + subqueryConditions = new Dictionary(); + FetchXmlScan leftFetch; if (joins.Count == 0) @@ -812,6 +814,8 @@ private bool FoldInExistsToFetchXml(NodeCompilationContext context, IList hints) + private bool FoldFiltersToDataSources(NodeCompilationContext context, IList hints, Dictionary subqueryExpressions) { var foldedFilters = false; @@ -1133,7 +1201,16 @@ private bool FoldFiltersToDataSources(NodeCompilationContext context, IList> GetIgnoreAliasesByNode(NodeCompilationContext context) { var fetchXmlSources = GetFoldableSources(Source, context) @@ -1282,7 +1370,10 @@ private IEnumerable GetAliases(FetchXmlScan fetchXml) yield return fetchXml.Alias; foreach (var linkEntity in fetchXml.Entity.GetLinkEntities()) - yield return linkEntity.alias; + { + if (linkEntity.alias != null) + yield return linkEntity.alias; + } } private BooleanExpression ReplaceColumnNames(BooleanExpression filter, Dictionary replacements) @@ -1455,9 +1546,62 @@ public override void AddRequiredColumns(NodeCompilationContext context, IList barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, out filter filter) + private BooleanExpression ExtractFetchXMLFilters(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, FetchXmlScan fetchXmlScan, Dictionary subqueryExpressions, out filter filter) + { + var targetEntityName = fetchXmlScan.Entity.name; + var targetEntityAlias = fetchXmlScan.Alias; + var items = fetchXmlScan.Entity.Items; + + var subqueryConditions = new HashSet(); + var result = ExtractFetchXMLFilters(context, dataSource, criteria, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, subqueryConditions, out filter); + + if (result == criteria) + return result; + + // If we've used any subquery expressions we need to make sure all the conditions are for the same entity as we + // can't specify an entityname attribute for the subquery filter. + if (subqueryConditions.Count == 0) + return result; + + var subqueryLinks = subqueryConditions + .Select(c => subqueryExpressions[c].LinkEntity?.alias ?? targetEntityAlias) + .Distinct() + .ToList(); + + if (subqueryLinks.Count > 1) + { + filter = null; + return criteria; + } + + foreach (var condition in filter.GetConditions()) + { + if ((condition.entityname ?? targetEntityAlias) != subqueryLinks[0]) + { + filter = null; + return criteria; + } + + condition.entityname = null; + } + + foreach (var subqueryCondition in subqueryConditions) + RemoveJoin(subqueryExpressions[subqueryCondition].JoinNode); + + // If the criteria are to be applied to the root entity, no need to do any further processing + if (subqueryLinks[0] == targetEntityAlias) + return result; + + // Otherwise, add the filter directly to the link entity + var linkEntity = fetchXmlScan.Entity.FindLinkEntity(subqueryLinks[0]); + linkEntity.AddItem(filter); + filter = null; + return result; + } + + private BooleanExpression ExtractFetchXMLFilters(NodeCompilationContext context, DataSource dataSource, BooleanExpression criteria, INodeSchema schema, string allowedPrefix, HashSet barredPrefixes, string targetEntityName, string targetEntityAlias, object[] items, Dictionary subqueryExpressions, HashSet replacedSubqueryExpressions, out filter filter) { - if (TranslateFetchXMLCriteria(context, dataSource, criteria, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out filter)) + if (TranslateFetchXMLCriteria(context, dataSource, criteria, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpressions, out filter)) return null; if (!(criteria is BooleanBinaryExpression bin)) @@ -1466,8 +1610,8 @@ private BooleanExpression ExtractFetchXMLFilters(NodeCompilationContext context, if (bin.BinaryExpressionType != BooleanBinaryExpressionType.And) return criteria; - bin.FirstExpression = ExtractFetchXMLFilters(context, dataSource, bin.FirstExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out var lhsFilter); - bin.SecondExpression = ExtractFetchXMLFilters(context, dataSource, bin.SecondExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, out var rhsFilter); + bin.FirstExpression = ExtractFetchXMLFilters(context, dataSource, bin.FirstExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpressions, out var lhsFilter); + bin.SecondExpression = ExtractFetchXMLFilters(context, dataSource, bin.SecondExpression, schema, allowedPrefix, barredPrefixes, targetEntityName, targetEntityAlias, items, subqueryExpressions, replacedSubqueryExpressions, out var rhsFilter); filter = (lhsFilter != null && rhsFilter != null) ? new filter { Items = new object[] { lhsFilter, rhsFilter } } : lhsFilter ?? rhsFilter; diff --git a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs index 6c942b01..d0c70770 100644 --- a/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs +++ b/MarkMpn.Sql4Cds.Engine/ExecutionPlan/FoldableJoinNode.cs @@ -374,7 +374,7 @@ private bool FoldFetchXmlJoin(NodeCompilationContext context, IList [System.Xml.Serialization.XmlElementAttribute("condition", typeof(condition))] [System.Xml.Serialization.XmlElementAttribute("filter", typeof(filter))] + [System.Xml.Serialization.XmlElementAttribute("link-entity", typeof(FetchLinkEntityType))] public object[] Items { get { return this.itemsField; diff --git a/MarkMpn.Sql4Cds.Engine/FetchXmlExtensions.cs b/MarkMpn.Sql4Cds.Engine/FetchXmlExtensions.cs index ea0f9903..f743e71d 100644 --- a/MarkMpn.Sql4Cds.Engine/FetchXmlExtensions.cs +++ b/MarkMpn.Sql4Cds.Engine/FetchXmlExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Xml.Serialization; @@ -46,7 +47,7 @@ public static FetchLinkEntityType FindLinkEntity(object[] items, string alias) foreach (var linkEntity in items.OfType()) { - if (linkEntity.alias.Equals(alias, StringComparison.OrdinalIgnoreCase)) + if (linkEntity.alias != null && linkEntity.alias.Equals(alias, StringComparison.OrdinalIgnoreCase)) return linkEntity; var childMatch = FindLinkEntity(linkEntity.Items, alias); @@ -150,5 +151,32 @@ public static void RemoveAttributes(this FetchEntityType entity) link.Items = link.Items.Where(i => !(i is FetchAttributeType || i is allattributes)).ToArray(); } } + + public static FetchLinkEntityType RemoveNotNullJoinCondition(this FetchLinkEntityType linkEntity) + { + if (linkEntity.Items == null) + return linkEntity; + + foreach (var filter in linkEntity.Items.OfType()) + { + var notNull = filter.Items + .OfType() + .Where(c => c.attribute == linkEntity.from && c.entityname == null && c.@operator == @operator.notnull); + + filter.Items = filter.Items.Except(notNull).ToArray(); + } + + return linkEntity; + } + + + public static IEnumerable GetConditions(this filter filter) + { + return filter.Items + .OfType() + .Concat(filter.Items + .OfType() + .SelectMany(f => f.GetConditions())); + } } } diff --git a/MarkMpn.Sql4Cds.XTB/ConnectionPropertiesWrapper.cs b/MarkMpn.Sql4Cds.XTB/ConnectionPropertiesWrapper.cs index 96940451..d5bb2c06 100644 --- a/MarkMpn.Sql4Cds.XTB/ConnectionPropertiesWrapper.cs +++ b/MarkMpn.Sql4Cds.XTB/ConnectionPropertiesWrapper.cs @@ -15,22 +15,22 @@ public ConnectionPropertiesWrapper(ConnectionDetail connection) [Category("Connection")] [DisplayName("Connection Name")] - public string ConnectionName => _connection.ConnectionName; + public string ConnectionName => _connection?.ConnectionName; [Category("Connection")] [DisplayName("Connection Id")] - public Guid? ConnectionId => _connection.ConnectionId; + public Guid? ConnectionId => _connection?.ConnectionId; [Category("Organization")] [DisplayName("Organization Name")] - public string Organization => _connection.Organization; + public string Organization => _connection?.Organization; [Category("Organization")] [DisplayName("Version")] - public string OrganizationVersion => _connection.OrganizationVersion; + public string OrganizationVersion => _connection?.OrganizationVersion; [Category("Organization")] [DisplayName("URL")] - public string OrganizationServiceUrl => _connection.OrganizationServiceUrl; + public string OrganizationServiceUrl => _connection?.OrganizationServiceUrl; } } \ No newline at end of file diff --git a/MarkMpn.Sql4Cds.XTB/PluginControl.cs b/MarkMpn.Sql4Cds.XTB/PluginControl.cs index c9528f25..ea185727 100644 --- a/MarkMpn.Sql4Cds.XTB/PluginControl.cs +++ b/MarkMpn.Sql4Cds.XTB/PluginControl.cs @@ -111,16 +111,10 @@ private void AddConnection(ConnectionDetail con) if (!_dataSources.ContainsKey(con.ConnectionName)) { var metadata = new SharedMetadataCache(con, GetNewServiceClient(con)); + var tableSize = new TableSizeCache(GetNewServiceClient(con), metadata); + var messages = new MessageCache(GetNewServiceClient(con), metadata); - _dataSources[con.ConnectionName] = new XtbDataSource - { - ConnectionDetail = con, - Connection = GetNewServiceClient(con), - Metadata = metadata, - TableSizeCache = new TableSizeCache(GetNewServiceClient(con), metadata), - Name = con.ConnectionName, - MessageCache = new MessageCache(GetNewServiceClient(con), metadata) - }; + _dataSources[con.ConnectionName] = new XtbDataSource(con, GetNewServiceClient, metadata, tableSize, messages); } // Start loading the entity list in the background diff --git a/MarkMpn.Sql4Cds.XTB/XtbDataSource.cs b/MarkMpn.Sql4Cds.XTB/XtbDataSource.cs index 1c3c1e2c..806ed3a1 100644 --- a/MarkMpn.Sql4Cds.XTB/XtbDataSource.cs +++ b/MarkMpn.Sql4Cds.XTB/XtbDataSource.cs @@ -1,15 +1,24 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using MarkMpn.Sql4Cds.Engine; +using MarkMpn.Sql4Cds.Engine.ExecutionPlan; using McTools.Xrm.Connection; +using Microsoft.Xrm.Sdk; namespace MarkMpn.Sql4Cds.XTB { class XtbDataSource : DataSource { + public XtbDataSource(ConnectionDetail connection, Func connect, IAttributeMetadataCache metadata, ITableSizeCache tableSize, IMessageCache messages) : base(connect(connection), metadata, tableSize, messages) + { + ConnectionDetail = connection; + Name = connection.ConnectionName; + } + public ConnectionDetail ConnectionDetail { get; set; } } }