From 5b8999ce6e43c80547e1b596855d46914b0ca802 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 18 Nov 2024 16:55:13 +0500 Subject: [PATCH 1/7] Created tests for issues --- ...ub0402_GroupByExpressionPartsNotVisited.cs | 1773 +++++++++++++++++ ...eGitHub0402_UnusedINCausesWrongMappings.cs | 732 +++++++ 2 files changed, 2505 insertions(+) create mode 100644 Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs create mode 100644 Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_UnusedINCausesWrongMappings.cs diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs new file mode 100644 index 0000000000..d0b6e6f389 --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs @@ -0,0 +1,1773 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Issues.IssueGitHub0402_GroupByExpressionPartsNotVisitedModel; + +namespace Xtensive.Orm.Tests.Issues.IssueGitHub0402_GroupByExpressionPartsNotVisitedModel +{ + public static class GlAccountTypes + { + public const string Income = "Income"; + public const string IncomeIncome = "IncomeIncome"; + public const string IncomeOtherIncome = "OtherIncome"; + + public const string Loans = "Loans"; + public const string LoansPayable = "LoansPayable"; + public const string LoansDeferredRevenue = "LoansDeferredRevenue"; + + public const string Assets = "Assets"; + public const string AssetsCash = "AssetsCash"; + public const string AssetsPrepaidExpenses = "AssetsPrepaidExpenses"; + } + + [HierarchyRoot] + public class Invoice : Entity + { + [Field, Key] + public long InvoiceId { get; private set; } + + [Field] + public string Name { get; set; } + + [Field] + public DateTime CreatedOn { get; private set; } + + [Field, Association(PairTo = nameof(InvoiceItem.Invoice))] + public EntitySet Items { get; set; } + + [Field] + public decimal Subtotal { get; set; } + + public void CacheCalculateSubtotal() + { + this.Session.SaveChanges(); + Subtotal = Items.Select(i => i.Total).Sum(); + } + + public Invoice(Session session) + : base(session) + { + CreatedOn = DateTime.UtcNow.Date.AddDays(-100 + InvoiceId); + } + + public Invoice(Session session, string name) + : base(session) + { + CreatedOn = DateTime.UtcNow.Date.AddDays(-100 + InvoiceId); + Name = name; + + } + } + + [HierarchyRoot] + public class InvoiceItem : Entity + { + [Field, Key] + public long InvoiceItemId { get; private set; } + + [Field] + public Invoice Invoice { get; set; } + + [Field] + public GeneralLedgerAccount GeneralLedgerAccount { get; set; } + + [Field] + public BusinessUnit BusinessUnit { get; set; } + + [Field] + public string Name { get; set; } + + [Field] + public decimal Total { get; set; } + + public InvoiceItem(Session session, Invoice invoice) + : base(session) + { + Invoice = invoice; + Name = invoice.Name + "item #" + InvoiceItemId; + } + } + + [HierarchyRoot] + public class GeneralLedgerAccount : Entity + { + [Field, Key] + public long AccountId { get; private set; } + + [Field] + public string TypeName { get; set; } + + [Field] + public string Name { get; set; } + + public GeneralLedgerAccount(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class Job : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public Invoice Invoice { get; set; } + + [Field] + public BusinessUnit BusinessUnit { get; set; } + + [Field] + public string Name { get; set; } + + public Job(Session session) + : base(session) + { + } + } + + [HierarchyRoot] + public class BusinessUnit : Entity + { + [Field, Key] + public long Id { get; private set; } + + [Field] + public string Name { get; set; } + + public BusinessUnit(Session session) + : base(session) + { + } + } +} + +namespace Xtensive.Orm.Tests.Issues +{ + public sealed class IssueGitHub0402_GroupByExpressionPartsNotVisited : AutoBuildTest + { + private readonly List accessibleBusinessUnits = new List(); + + protected override DomainConfiguration BuildConfiguration() + { + var domainConfiguration = base.BuildConfiguration(); + domainConfiguration.Types.Register(typeof(Invoice).Assembly, typeof(Invoice).Namespace); + domainConfiguration.UpgradeMode = DomainUpgradeMode.Recreate; + + return domainConfiguration; + } + + protected override void PopulateData() + { + var glAccountsContainerType = typeof(GlAccountTypes); + var allTypes = glAccountsContainerType.GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static) + .Where(fi => fi.IsLiteral && !fi.IsInitOnly); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + PopulateEntities(session, allTypes); + + tx.Complete(); + } + } + + [Test] + public void Case01Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(18)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case02Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB.anonInst, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.job.Id, + gg => (gg.filteredB.item != null) + ? gg.itemRevenue + : gg.filteredB.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB.anonInst, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.job.Id, + gg => (gg.filteredB.item != null) + ? gg.itemRevenue + : gg.filteredB.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(18)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case03Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB.anonInst, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.job.Id, + gg => (gg.filteredB.item != null) + ? gg.itemRevenue + : gg.filteredB.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB.anonInst, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.job.Id, + gg => (gg.filteredB.item != null) + ? gg.itemRevenue + : gg.filteredB.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(20)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case04Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.anonB.anonInst.job, + item = b.anonB.anonInst.item, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.anonB.anonInst.job, + item = b.anonB.anonInst.item, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(18)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case05Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.anonB.anonInst.job, + item = b.anonB.anonInst.item, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.anonB.anonInst.job, + item = b.anonB.anonInst.item, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(20)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case06Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.anonInst.job, + item = b.anonInst.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.anonInst.job, + item = b.anonInst.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(18)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case07Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.anonInst.job, + item = b.anonInst.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.anonInst.job, + item = b.anonInst.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(20)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case08Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.job, + item = b.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.item != null) && (b.item.BusinessUnit != null)) + ? b.item.BusinessUnit.Id + : b.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.job, + item = b.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.item != null) && (b.item.BusinessUnit != null)) + ? b.item.BusinessUnit.Id + : b.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(18)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case09Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var groupByResults = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.job, + item = b.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.item != null) && (b.item.BusinessUnit != null)) + ? b.item.BusinessUnit.Id + : b.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + var groupByResultsLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + job = b.job, + item = b.item, + isIncomeItem = b.isIncomeItem, + itemBusinessUnitId = ((b.item != null) && (b.item.BusinessUnit != null)) + ? b.item.BusinessUnit.Id + : b.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .ToList(); + + Assert.That(groupByResultsLocal.Count, Is.EqualTo(20)); + Assert.That(groupByResults.Count, Is.EqualTo(groupByResultsLocal.Count)); + + groupByResults.Sort((a, b) => a.Key.CompareTo(b.Key)); + groupByResultsLocal.Sort((a, b) => a.Key.CompareTo(b.Key)); + + for (int i = 0, count = groupByResults.Count; i < count; i++) { + var server = groupByResults[i]; + var local = groupByResultsLocal[i]; + Assert.That(server.Key, Is.EqualTo(local.Key)); + + var serverGroupContent = server.ToList(); + var localGroupContent = local.ToList(); + + Assert.That(serverGroupContent.Count, Is.EqualTo(localGroupContent.Count)); + serverGroupContent.Sort(); + localGroupContent.Sort(); + + for (int j = 0, gCount = serverGroupContent.Count; j < gCount; j++) { + Assert.That(serverGroupContent[j], Is.EqualTo(localGroupContent[j])); + } + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case10Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(18)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + + Assert.That(otherRevenues.Count, Is.GreaterThan(0)); + } + } + + [Test] + public void Case11Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(20)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + + Assert.That(otherRevenues.Count, Is.GreaterThan(0)); + } + } + + [Test] + public void Case12Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonInst.job.Id, + gg => (gg.filteredB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b.anonB, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonInst.job.Id, + gg => (gg.filteredB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(18)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + + Assert.That(otherRevenues.Count, Is.GreaterThan(0)); + } + } + + [Test] + public void Case13Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable() + .Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }) + }) + .Select(b => new { + anonB = b, + itemBusinessUnitId = ((b.anonInst.item != null) && (b.anonInst.item.BusinessUnit != null)) + ? b.anonInst.item.BusinessUnit.Id + : b.anonInst.job.BusinessUnit.Id + }) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.anonB.isIncomeItem + ? b.anonB.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonB.anonInst.job.Id, + gg => (gg.filteredB.anonB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(20)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + + Assert.That(otherRevenues.Count, Is.GreaterThan(0)); + } + } + + [Test] + public void Case14Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All().Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }), + itemBusinessUnitId = ((a.item != null) && (a.item.BusinessUnit != null)) + ? a.item.BusinessUnit.Id + : a.job.BusinessUnit.Id, + }) + .Where(b => b.itemBusinessUnitId != businessUnitId && (allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId))) + .Select(b => new { + filteredB = b, + itemRevenue = b.isIncomeItem + ? b.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonInst.job.Id, + gg => (gg.filteredB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable().Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + anonInst = a, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }), + itemBusinessUnitId = ((a.item != null) && (a.item.BusinessUnit != null)) + ? a.item.BusinessUnit.Id + : a.job.BusinessUnit.Id, + }) + .Where(b => b.itemBusinessUnitId != businessUnitId) + .Where(b => allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId)) + .Select(b => new { + filteredB = b, + itemRevenue = b.isIncomeItem + ? b.anonInst.item.Total + : 0 + }) + .GroupBy(g => g.filteredB.anonInst.job.Id, + gg => (gg.filteredB.anonInst.item != null) + ? gg.itemRevenue + : gg.filteredB.anonInst.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(18)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + [Test] + public void Case15Test() + { + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); + var businessUnitId = 10; + var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); + + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + session.Events.DbCommandExecuting += Events_DbCommandExecuting; + session.Events.QueryExecuting += Events_QueryExecuting; + + var otherRevenues = session.Query.All() + .SelectMany( + job => session.Query.All().Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }), + itemBusinessUnitId = ((a.item != null) && (a.item.BusinessUnit != null)) + ? a.item.BusinessUnit.Id + : a.job.BusinessUnit.Id, + }) + .Where(b => b.itemBusinessUnitId != businessUnitId && (allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId))) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + + var otherRevenuesLocal = session.Query.All().AsEnumerable() + .SelectMany( + job => session.Query.All().AsEnumerable().Where(item => item.Invoice == job.Invoice).DefaultIfEmpty(), + (job, item) => new { job = job, item = item }) + .Select(a => new { + job = a.job, + item = a.item, + isIncomeItem = (a.item.GeneralLedgerAccount == null) || a.item.GeneralLedgerAccount.TypeName + .In(new[] { GlAccountTypes.Income, GlAccountTypes.IncomeIncome, GlAccountTypes.IncomeOtherIncome }), + itemBusinessUnitId = ((a.item != null) && (a.item.BusinessUnit != null)) + ? a.item.BusinessUnit.Id + : a.job.BusinessUnit.Id, + }) + .Where(b => b.itemBusinessUnitId != businessUnitId + && (allBusinessUnitsAccessible || accessibleBusinessUnitIds.Contains(b.itemBusinessUnitId))) + .Select(b => new { + job = b.job, + item = b.item, + itemRevenue = b.isIncomeItem + ? b.item.Total + : 0 + }) + .GroupBy(g => g.job.Id, + gg => (gg.item != null) + ? gg.itemRevenue + : gg.job.Invoice.Subtotal) + .Select(items => new { JobId = items.Key, Revenue = items.Sum() }) + .ToList(); + + Assert.That(otherRevenuesLocal.Count, Is.EqualTo(18)); + Assert.That(otherRevenues.Count, Is.EqualTo(otherRevenuesLocal.Count)); + + otherRevenues.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + otherRevenuesLocal.Sort((a, b) => a.JobId.CompareTo(b.JobId)); + + for (int i = 0, count = otherRevenues.Count; i < count; i++) { + var server = otherRevenues[i]; + var local = otherRevenuesLocal[i]; + Assert.That(server.JobId, Is.EqualTo(local.JobId)); + Assert.That(server.Revenue, Is.EqualTo(local.Revenue)); + } + + session.Events.QueryExecuting -= Events_QueryExecuting; + session.Events.DbCommandExecuting -= Events_DbCommandExecuting; + } + } + + private void PopulateEntities(Session session, IEnumerable allAccountTypes) + { + var glAccounts = new List(15); + foreach (var constantField in allAccountTypes) { + var glAccount = new GeneralLedgerAccount(session) { TypeName = (string) constantField.GetValue(null) }; + glAccounts.Add(glAccount); + } + + while (glAccounts.Count < 15) { + glAccounts.Add(null); + } + + var businessUnits = new List(7); + + for (var i = 0; i < 7; i++) { + var bUnit = new BusinessUnit(session) { Name = $"Unit #{i + 1}" }; + businessUnits.Add(bUnit); + if (i < 6) { + accessibleBusinessUnits.Add(bUnit.Id); + } + } + + session.SaveChanges(); + + var indexForName = 1; + + // invoices entirely for one business unit each + foreach (var bU in businessUnits) { + var invoice1 = new Invoice(session) { Subtotal = 0, Name = $"invoice #{indexForName}" }; + var job1 = new Job(session) { Invoice = invoice1, BusinessUnit = bU, Name = $"job #{indexForName}" }; + + var invoice2 = new Invoice(session) { Subtotal = 0, Name = $"invoice #{indexForName + 1}" }; + var job2 = new Job(session) { Invoice = invoice1, BusinessUnit = bU, Name = $"job #{indexForName + 1}" }; + + //these gets business unit from job + foreach (var account in glAccounts) { + var invoiceItem1 = new InvoiceItem(session, invoice1) { + GeneralLedgerAccount = account, + BusinessUnit = null, + Total = ((account != null ? account.AccountId : 0) + invoice1.InvoiceId + job1.Id) / indexForName + }; + var invoiceItem2 = new InvoiceItem(session, invoice2) { + GeneralLedgerAccount = account, + BusinessUnit = null, + Total = ((account != null ? account.AccountId : 5) + invoice2.InvoiceId + job1.Id) / indexForName + }; + } + indexForName += 2; + } + + session.SaveChanges(); + + //invoices with all invoice items of different business units + var sharedInvoice = new Invoice(session) { Subtotal = 0, Name = $"shared invoice #0" }; + var sharedInvoceJob = new Job(session) { Invoice = sharedInvoice, BusinessUnit = businessUnits[0], Name = $"shared job #0" }; + + indexForName = 1; + foreach (var account in glAccounts.Where(b => b != null)) { + foreach (var bU in businessUnits) { + var invoiceItem = new InvoiceItem(session, sharedInvoice) { + GeneralLedgerAccount = account, + BusinessUnit = bU, + Total = (account.AccountId + sharedInvoice.InvoiceId + sharedInvoceJob.Id) / indexForName + }; + indexForName++; + } + } + + session.SaveChanges(); + + // invoices with default business unit in job and defined in invoiceItem + for (var i = 0; i < businessUnits.Count; i++) { + var bU = businessUnits[i]; + var mixedInvoice = new Invoice(session) { Subtotal = 0, Name = $"mixed invoice #{i + 1}" }; + var mixedInvoceJob = new Job(session) { Invoice = sharedInvoice, BusinessUnit = bU, Name = $"mixed job #{i + 1}" }; + + indexForName = 1; + foreach (var account in glAccounts) { + foreach (var bbU in businessUnits) { + var invoiceItem = new InvoiceItem(session, sharedInvoice) { + GeneralLedgerAccount = account, + BusinessUnit = bU == bbU ? null : bbU, + Total = ((account != null ? account.AccountId : 10) + sharedInvoice.InvoiceId + mixedInvoceJob.Id) / indexForName + }; + indexForName++; + } + } + } + session.SaveChanges(); + } + + + private void Events_QueryExecuting(object sender, QueryEventArgs e) + { + Console.WriteLine("The Query executing"); + Console.WriteLine(e.Expression.ToString()); + } + + private void Events_DbCommandExecuting(object sender, DbCommandEventArgs e) + { + Console.WriteLine("Command Text:"); + Console.WriteLine(e.Command.CommandText); + foreach (System.Data.Common.DbParameter param in e.Command.Parameters) { + Console.WriteLine(param.ParameterName + " = " + param.Value.ToString()); + } + } + } +} diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_UnusedINCausesWrongMappings.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_UnusedINCausesWrongMappings.cs new file mode 100644 index 0000000000..d63b0948ec --- /dev/null +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_UnusedINCausesWrongMappings.cs @@ -0,0 +1,732 @@ +// Copyright (C) 2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Xtensive.Core; +using Xtensive.Orm.Configuration; +using Xtensive.Orm.Tests.Issues.IssueGitHub0402_UnusedINCausesWrongMappingsModel; + +namespace Xtensive.Orm.Tests.Issues.IssueGitHub0402_UnusedINCausesWrongMappingsModel +{ + [HierarchyRoot] + public class Invoice : Entity + { + [Field, Key] + public long InvoiceId { get; private set; } + + [Field] + public long PseudoId { get; set; } + + [Field] + public string Name { get; set; } + + [Field] + public DateTime CreatedOn { get; private set; } + + [Field] + public decimal Subtotal { get; set; } + + + public Invoice(Session session) + : base(session) + { + CreatedOn = DateTime.UtcNow.Date.AddDays(-100 + InvoiceId); + PseudoId = InvoiceId; + } + + public Invoice(Session session, string name) + : base(session) + { + CreatedOn = DateTime.UtcNow.Date.AddDays(-100 + InvoiceId); + PseudoId = InvoiceId; + Name = name; + + } + } + + public static class FakeExtensions + { + public static bool InSome(this T source, params T[] values) => + InSome(source, (IEnumerable) values); + + public static bool InSome(this T source, IEnumerable values) => + values == null ? false : values.Contains(source); + + public static bool InSome(this T source, IncludeAlgorithm algorithm, params T[] values) => + InSome(source, algorithm, (IEnumerable) values); + +#pragma warning disable IDE0060 // Remove unused parameter + public static bool InSome(this T source, IncludeAlgorithm algorithm, IEnumerable values) => + values == null ? false : values.Contains(source); +#pragma warning restore IDE0060 // Remove unused parameter + + } +} + +namespace Xtensive.Orm.Tests.Issues +{ + public sealed class IssueGitHub0402_UnusedINCausesWrongMappings : AutoBuildTest + { + private readonly long[] nonExistingIds = new long[3]; + private readonly long[] existingIds = new long[4]; + + private readonly string[] nonExistingNames = new string[3]; + private readonly string[] existingNames = new string[4]; + + private readonly DateTime[] nonExistingDates = new DateTime[3]; + private readonly DateTime[] existingDates = new DateTime[4]; + + protected override DomainConfiguration BuildConfiguration() + { + var domainConfiguration = base.BuildConfiguration(); + domainConfiguration.Types.Register(typeof(Invoice).Assembly, typeof(Invoice).Namespace); + domainConfiguration.UpgradeMode = DomainUpgradeMode.Recreate; + + return domainConfiguration; + } + + protected override void PopulateData() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var i1 = new Invoice(session, "A"); + var i2 = new Invoice(session, "B"); + var i3 = new Invoice(session, "C"); + var i4 = new Invoice(session, "D"); + + nonExistingIds[0] = -(existingIds[0] = i1.InvoiceId); + nonExistingIds[1] = -(existingIds[1] = i2.InvoiceId); + nonExistingIds[2] = -(existingIds[2] = i3.InvoiceId); + existingIds[3] = i4.InvoiceId; + + existingNames[0] = i1.Name; + existingNames[1] = i2.Name; + existingNames[2] = i3.Name; + existingNames[3] = i4.Name; + + nonExistingNames[0] = "E"; + nonExistingNames[1] = "F"; + nonExistingNames[2] = "G"; + + existingDates[0] = i1.CreatedOn; + existingDates[1] = i2.CreatedOn; + existingDates[2] = i3.CreatedOn; + existingDates[3] = i4.CreatedOn; + + nonExistingDates[0] = DateTime.UtcNow.AddDays(1); + nonExistingDates[1] = DateTime.UtcNow.AddDays(2); + nonExistingDates[2] = DateTime.UtcNow.AddDays(3); + + tx.Complete(); + } + } + + [Test] + public void OriginalLinqQueryCaseTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result = (from invoice in session.Query.All() + let inNonExisting = invoice.InvoiceId.In(nonExistingIds) // This line incorrectly works as additional filter. + // Must not have any impact + where invoice.InvoiceId.In(existingIds) + select invoice) + .Count(); + + Assert.That(result, Is.EqualTo(existingIds.Length)); + } + } + + [Test] + public void MethodsVersionTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void ExplicitIncludeAlgorithmTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void TwoINsWithinSelectOrder2Test() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inExisting = invoice.InvoiceId.In(existingIds), + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.inNonExisting) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inExisting = invoice.InvoiceId.In(existingIds), + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.inNonExisting) + .Select(filtered => new { filtered.invoice, filtered.inExisting, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(0)); + Assert.That(result2.Length, Is.EqualTo(0)); + } + } + + [Test] + public void TwoINsWithExplicitAlgorithmWithinSelectOrder2Test() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds), + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.inNonExisting) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds), + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.inNonExisting) + .Select(filtered => new { filtered.invoice, filtered.inExisting, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(0)); + Assert.That(result2.Length, Is.EqualTo(0)); + } + } + + [Test] + public void TwoINsWithinSelectOrder3Test() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds), + inExisting = invoice.InvoiceId.In(existingIds), + }) + .Where(a => a.inExisting) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds), + inExisting = invoice.InvoiceId.In(existingIds), + }) + .Where(a => a.inExisting) + .Select(filtered => new { filtered.invoice, filtered.inExisting, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.inExisting == true), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void TwoINsWithExplicitAlgorithmWithinSelectOrder3Test() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds), + inExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds), + }) + .Where(a => a.inExisting) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds), + inExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds), + }) + .Where(a => a.inExisting) + .Select(filtered => new { filtered.invoice, filtered.inExisting, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.inExisting == true), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void PseudoIdWithinINTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.PseudoId.In(nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.PseudoId.In(nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void PseudoIdWithinINWithExplicitAlgorithmTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.PseudoId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.PseudoId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsByPseudoIdAndSelectedFieldTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice1 = invoice, + invoice2Id = invoice.InvoiceId, + inNonExisting = invoice.PseudoId.In(nonExistingIds) + }) + .Where(a => a.invoice2Id.In(existingIds)) + .Select(filtered => filtered.invoice1) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice1 = invoice, + invoice2Id = invoice.InvoiceId, + inNonExisting = invoice.PseudoId.In(nonExistingIds) + }) + .Where(a => a.invoice2Id.In(existingIds)) + .Select(filtered => new { filtered.invoice1, filtered.invoice2Id, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice1.InvoiceId.In(existingIds)), Is.True); + Assert.That(result2.All(r => r.invoice2Id.In(existingIds)), Is.True); + } + } + + [Test] + public void INsWithExplicitAlgorithmByPseudoIdAndSelectedFieldTest() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice1 = invoice, + invoice2Id = invoice.InvoiceId, + inNonExisting = invoice.PseudoId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice2Id.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice1) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice1 = invoice, + invoice2Id = invoice.InvoiceId, + inNonExisting = invoice.PseudoId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice2Id.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice1, filtered.invoice2Id, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice1.InvoiceId.In(existingIds)), Is.True); + Assert.That(result2.All(r => r.invoice2Id.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesTest1() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.Name.In(IncludeAlgorithm.ComplexCondition, nonExistingNames) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.Name.In(IncludeAlgorithm.ComplexCondition, nonExistingNames) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesWithExplicitAlgorithmTest1() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.Name.In(IncludeAlgorithm.ComplexCondition, nonExistingNames) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.Name.In(IncludeAlgorithm.ComplexCondition, nonExistingNames) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesTest2() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.CreatedOn.In(nonExistingDates) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.CreatedOn.In(nonExistingDates) + }) + .Where(a => a.invoice.InvoiceId.In(existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesWithExplicitAlgorithmTest2() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.CreatedOn.In(IncludeAlgorithm.ComplexCondition, nonExistingDates) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.CreatedOn.In(IncludeAlgorithm.ComplexCondition, nonExistingDates) + }) + .Where(a => a.invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, existingIds)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingIds.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingIds.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesTest3() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.Name.In(existingNames)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.Name.In(existingNames)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingNames.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingNames.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesWithExplicitAlgorithmTest3() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.Name.In(IncludeAlgorithm.ComplexCondition, existingNames)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.Name.In(IncludeAlgorithm.ComplexCondition, existingNames)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingNames.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingNames.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesTest4() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.CreatedOn.In(existingDates)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(nonExistingIds) + }) + .Where(a => a.invoice.CreatedOn.In(existingDates)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingDates.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingDates.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + + [Test] + public void INsOfDifferentTypesWithExplicitAlgorithmTest4() + { + using (var session = Domain.OpenSession()) + using (var tx = session.OpenTransaction()) { + + var result1 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.CreatedOn.In(IncludeAlgorithm.ComplexCondition, existingDates)) + .Select(filtered => filtered.invoice) + .ToArray(); + + var result2 = session.Query.All() + .Select(invoice => new { + invoice = invoice, + inNonExisting = invoice.InvoiceId.In(IncludeAlgorithm.ComplexCondition, nonExistingIds) + }) + .Where(a => a.invoice.CreatedOn.In(IncludeAlgorithm.ComplexCondition, existingDates)) + .Select(filtered => new { filtered.invoice, filtered.inNonExisting }) + .ToArray(); + + Assert.That(result1.Length, Is.EqualTo(existingDates.Length)); + + Assert.That(result2.Length, Is.EqualTo(existingDates.Length)); + Assert.That(result2.All(r => r.inNonExisting == false), Is.True); + Assert.That(result2.All(r => r.invoice.InvoiceId.In(existingIds)), Is.True); + } + } + } +} From 6811886fb13a05e8232ba0d92e0174965636f88b Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Thu, 21 Nov 2024 19:16:06 +0500 Subject: [PATCH 2/7] Restrain ability to skip member expressions Expressions' visit can be skipped only in order by or group by and for less expression types --- .../Orm/Linq/ExpressionExtensions.cs | 23 +++++--- .../Orm/Linq/Translator.Expressions.cs | 55 ++++++++++++------- .../Orm/Linq/Translator.Queryable.cs | 1 + Orm/Xtensive.Orm/Orm/Linq/TranslatorState.cs | 11 +++- 4 files changed, 62 insertions(+), 28 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs index fb2202c9b6..bff96a3a9c 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2008-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Alexey Kochetov // Created: 2008.12.02 @@ -89,6 +89,11 @@ private static bool IsForeignQuery(Expression expression) return false; } + public static bool IsExtendedExpression(this Expression expression) + { + return (ExtendedExpressionType) expression.StripMarkers().NodeType >= ExtendedExpressionType.Projection; + } + public static bool IsItemProjector(this Expression expression) { expression = expression.StripMarkers(); @@ -151,12 +156,16 @@ public static Type GetGroupingKeyType(this Expression expression) public static Expression StripMarkers(this Expression e) { - var ee = e as ExtendedExpression; - if (ee!=null && ee.ExtendedType==ExtendedExpressionType.Marker) { - var marker = (MarkerExpression) ee; - return marker.Target; + if ((ExtendedExpressionType)e.NodeType==ExtendedExpressionType.Marker) { + return ((MarkerExpression) e).Target; } return e; + //var ee = e as ExtendedExpression; + //if (ee!=null && ee.ExtendedType==ExtendedExpressionType.Marker) { + // var marker = (MarkerExpression) ee; + // return marker.Target; + //} + //return e; } public static bool IsMarker(this Expression e) diff --git a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs index f1177d9fb2..14f3c13cfd 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/Translator.Expressions.cs @@ -1343,26 +1343,41 @@ private Expression GetMember(Expression expression, MemberInfo member, Expressio if (memberIndex < 0) throw new InvalidOperationException(string.Format(Strings.ExCouldNotGetMemberXFromExpression, member)); var argument = newExpression.Arguments[memberIndex]; - var currentLambda = state.CurrentLambda; - if (!(currentLambda is null) && context.Bindings.TryGetValue(currentLambda.Parameters[0], out var projection)) { - // Here, we try to detect whether the expression has already visited, in such cases - // re-visiting it may cause issues. For instance, .OrderBy(x=>x.SomeField) translation - // gets projection of already visited source and substitutes parameter 'x' access with - // that projection. For now TranslatorState.ShouldOmitConvertToObject == true, is a marker - // of OrderBy visiting, because it is the only place that sets 'true' to the property, - // but we can't rely on it for future-proof solution. - - // Knowing that parameter-to-projection binding happens when expression has visited, - // we assume this check is enough for certain cases, probably not for all of them. - // We can't deny visiting of the argument for all the cases because of chance to break - // some cases we could not imagine at the time of changes - switch (argument.NodeType) { - case ExpressionType.TypeAs: - case ExpressionType.Convert: - case ExpressionType.ConvertChecked: - if (argument.Type == typeof(object) && state.ShouldOmitConvertToObject) - argument = argument.StripCasts(); - break; + + if (state.GroupingKey || state.OrderingKey) { + var requireVisit = !argument.IsExtendedExpression() && argument.NodeType switch { + ExpressionType.New => true, + ExpressionType.Call => true, + ExpressionType.Coalesce => true, + ExpressionType.Conditional => true, + _ => false + }; + + var currentLambda = state.CurrentLambda; + if (!(currentLambda is null) && context.Bindings.TryGetValue(currentLambda.Parameters[0], out var projection) + && !requireVisit) { + // Here, we try to detect whether the expression has already visited, in such cases + // re-visiting it may cause issues. For instance, .OrderBy(x=>x.SomeField) translation + // gets projection of already visited source and substitutes parameter 'x' access with + // that projection. For now TranslatorState.ShouldOmitConvertToObject == true, is a marker + // of OrderBy visiting, because it is the only place that sets 'true' to the property, + // but we can't rely on it for future-proof solution. + + // Knowing that parameter-to-projection binding happens when expression has visited, + // we assume this check is enough for certain cases, probably not for all of them. + // We can't deny visiting of the argument for all the cases because of chance to break + // some cases we could not imagine at the time of changes + switch (argument.NodeType) { + case ExpressionType.TypeAs: + case ExpressionType.Convert: + case ExpressionType.ConvertChecked: + if (argument.Type == typeof(object) && state.ShouldOmitConvertToObject) + argument = argument.StripCasts(); + break; + } + } + else { + argument = Visit(argument); } } else { diff --git a/Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs b/Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs index 3ae8a54b3f..ffbf273ad7 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/Translator.Queryable.cs @@ -988,6 +988,7 @@ private Expression VisitSort(Expression expression) using (state.CreateScope()) { state.ShouldOmitConvertToObject = true; state.CalculateExpressions = true; + state.OrderingKey = true; var orderByProjector = (ItemProjectorExpression) VisitLambda(sortExpression); var columns = orderByProjector .GetColumns(ColumnExtractionModes.TreatEntityAsKey | ColumnExtractionModes.Distinct); diff --git a/Orm/Xtensive.Orm/Orm/Linq/TranslatorState.cs b/Orm/Xtensive.Orm/Orm/Linq/TranslatorState.cs index 1479042d9a..193aa2bdba 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/TranslatorState.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/TranslatorState.cs @@ -25,7 +25,8 @@ private enum TranslatorStateFlags ShouldOmitConvertToObject = 1 << 6, RequestCalculateExpressions = 1 << 7, RequestCalculateExpressionsOnce = 1 << 8, - SkipNullableColumnsDetectionInGroupBy = 1 << 9 + SkipNullableColumnsDetectionInGroupBy = 1 << 9, + IsOrderingKey = 1 << 10, } internal readonly ref struct TranslatorScope @@ -105,6 +106,14 @@ public bool GroupingKey : flags & ~TranslatorStateFlags.IsGroupingKey; } + public bool OrderingKey + { + get => (flags & TranslatorStateFlags.IsOrderingKey) != 0; + set => flags = value + ? flags | TranslatorStateFlags.IsOrderingKey + : flags & ~TranslatorStateFlags.IsOrderingKey; + } + public bool IsTailMethod { get => (flags & TranslatorStateFlags.IsTailMethod) != 0; From 06fce7c232502e34c1124c9c916b6c18ab2edfca Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 26 Nov 2024 18:49:29 +0500 Subject: [PATCH 3/7] IncludeProvider: Add column to the end of source header with next index instead of const 0 --- .../Orm/Rse/Providers/Compilable/IncludeProvider.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/IncludeProvider.cs b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/IncludeProvider.cs index 7aa6c54186..aee5999c97 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/IncludeProvider.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Providers/Compilable/IncludeProvider.cs @@ -1,6 +1,6 @@ -// Copyright (C) 2003-2010 Xtensive LLC. -// All rights reserved. -// For conditions of distribution and use, see license. +// Copyright (C) 2009-2024 Xtensive LLC. +// This code is distributed under MIT license terms. +// See the License.txt file in the project root for more information. // Created by: Alexey Gamzov // Created: 2009.10.22 @@ -62,7 +62,7 @@ public sealed class IncludeProvider: UnaryProvider, /// protected override RecordSetHeader BuildHeader() { - var newHeader = Source.Header.Add(new SystemColumn(ResultColumnName, 0, BoolType)); + var newHeader = Source.Header.Add(new SystemColumn(ResultColumnName, Source.Header.Length, BoolType)); var fieldTypes = new Type[FilteredColumns.Count]; for (var index = 0; index < fieldTypes.Length; index++) { fieldTypes[index] = newHeader.Columns[FilteredColumns[index]].Type; From 8b9fba7b321cc9f14e69370bfc11f3985b04bb45 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Tue, 26 Nov 2024 19:01:33 +0500 Subject: [PATCH 4/7] IncludeProvider's hidden column index taken to account while columns optimization --- .../Transformation/ColumnMappingInspector.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs b/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs index 5785d79c8b..0e20f156b3 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs @@ -42,7 +42,10 @@ protected override Provider VisitInclude(IncludeProvider provider) var sourceLength = provider.Source.Header.Length; mappings[provider.Source] = Merge(mappings[provider].Where(i => i < sourceLength), provider.FilteredColumns); var source = VisitCompilable(provider.Source); - mappings[provider] = Merge(mappings[provider], mappings[provider.Source]); + + var currentMapping = mappings[provider.Source]; + var calulatedColumn = provider.Header.Columns.Last(); + mappings[provider] = Merge(currentMapping, EnumerableUtils.One(calulatedColumn.Index)); if (source == provider.Source) { return provider; } @@ -110,14 +113,36 @@ protected override Provider VisitSeek(SeekProvider provider) protected override Provider VisitFilter(FilterProvider provider) { - mappings[provider.Source] = Merge(mappings[provider], mappingsGatherer.Gather(provider.Predicate)); + var gatheredMappings = mappingsGatherer.Gather(provider.Predicate); + var originalMappingsOfProvider = mappings[provider]; + var mergedMappings = Merge(originalMappingsOfProvider, gatheredMappings); + + mappings[provider.Source] = mergedMappings; var newSourceProvider = VisitCompilable(provider.Source); - mappings[provider] = mappings[provider.Source]; + var updatedSourceMappings = mappings[provider.Source]; + mappings[provider] = updatedSourceMappings; - var predicate = TranslateLambda(provider, provider.Predicate); - return newSourceProvider == provider.Source && predicate == provider.Predicate + var newPredicate = TranslateLambda(provider, provider.Predicate); + return newSourceProvider == provider.Source && newPredicate == provider.Predicate ? provider - : new FilterProvider(newSourceProvider, (Expression>) predicate); + : new FilterProvider(newSourceProvider, (Expression>) newPredicate); + + + //var isProviderTheSame = newSourceProvider == provider.Source; + //if (isProviderTheSame) { + // // If new source provider is the same as old provider.Source then there must be no changes in its mappings. + // // No remap needed for predicate. + // //if (TranslateLambda(provider, provider.Predicate) != provider.Predicate) + // // throw new Exception("AAAA!!!! Mappings went wrong!!!"); + // return provider; + //} + //else { + // // otherwise, there is a chance that new mappings should be applied to predicate + // var newPredicate = TranslateLambda(provider, provider.Predicate); + // return newPredicate == provider.Predicate + // ? provider + // : new FilterProvider(newSourceProvider, (Expression>) newPredicate); + //} } protected override Provider VisitJoin(JoinProvider provider) From ad31f4f34006b01a58042ea3506db31535908277 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 4 Dec 2024 16:51:05 +0500 Subject: [PATCH 5/7] Ignore serveral tests because queries require Cross Apply --- ...sueGitHub0402_GroupByExpressionPartsNotVisited.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs index d0b6e6f389..9aff96beb7 100644 --- a/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs +++ b/Orm/Xtensive.Orm.Tests/Issues/IssueGitHub0402_GroupByExpressionPartsNotVisited.cs @@ -1116,6 +1116,8 @@ public void Case09Test() [Test] public void Case10Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); @@ -1211,6 +1213,8 @@ public void Case10Test() [Test] public void Case11Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); @@ -1304,6 +1308,8 @@ public void Case11Test() [Test] public void Case12Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); @@ -1399,6 +1405,8 @@ public void Case12Test() [Test] public void Case13Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); @@ -1492,6 +1500,8 @@ public void Case13Test() [Test] public void Case14Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); @@ -1577,6 +1587,8 @@ public void Case14Test() [Test] public void Case15Test() { + Require.AllFeaturesSupported(Providers.ProviderFeatures.Apply); + var accessibleBusinessUnitIds = accessibleBusinessUnits.ToList(); var businessUnitId = 10; var allBusinessUnitsAccessible = !accessibleBusinessUnitIds.Any(id => id != 0); From 0a3ff1f5c9f68850a20548f315a164c826b5a0b9 Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Mon, 2 Dec 2024 12:16:46 +0500 Subject: [PATCH 6/7] Improve changelog --- ChangeLog/6.0.14_dev.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ChangeLog/6.0.14_dev.txt b/ChangeLog/6.0.14_dev.txt index e69de29bb2..dde41e5522 100644 --- a/ChangeLog/6.0.14_dev.txt +++ b/ChangeLog/6.0.14_dev.txt @@ -0,0 +1,2 @@ +[main] Addressed issue of not visiting grouping expression correctly which appeared in previous version +[main] Addressed certain issues of incorrect query optimization when it contains multiple In() calls \ No newline at end of file From d93aaf8b5801ff7e5b9d2d210d826d7e796e94aa Mon Sep 17 00:00:00 2001 From: Alexey Kulakov Date: Wed, 4 Dec 2024 18:18:46 +0500 Subject: [PATCH 7/7] Remove old commented code --- .../Orm/Linq/ExpressionExtensions.cs | 8 +------- .../Transformation/ColumnMappingInspector.cs | 17 ----------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs index bff96a3a9c..5da119add6 100644 --- a/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs +++ b/Orm/Xtensive.Orm/Orm/Linq/ExpressionExtensions.cs @@ -156,16 +156,10 @@ public static Type GetGroupingKeyType(this Expression expression) public static Expression StripMarkers(this Expression e) { - if ((ExtendedExpressionType)e.NodeType==ExtendedExpressionType.Marker) { + if ((ExtendedExpressionType) e.NodeType == ExtendedExpressionType.Marker) { return ((MarkerExpression) e).Target; } return e; - //var ee = e as ExtendedExpression; - //if (ee!=null && ee.ExtendedType==ExtendedExpressionType.Marker) { - // var marker = (MarkerExpression) ee; - // return marker.Target; - //} - //return e; } public static bool IsMarker(this Expression e) diff --git a/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs b/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs index 0e20f156b3..96169f18f3 100644 --- a/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs +++ b/Orm/Xtensive.Orm/Orm/Rse/Transformation/ColumnMappingInspector.cs @@ -126,23 +126,6 @@ protected override Provider VisitFilter(FilterProvider provider) return newSourceProvider == provider.Source && newPredicate == provider.Predicate ? provider : new FilterProvider(newSourceProvider, (Expression>) newPredicate); - - - //var isProviderTheSame = newSourceProvider == provider.Source; - //if (isProviderTheSame) { - // // If new source provider is the same as old provider.Source then there must be no changes in its mappings. - // // No remap needed for predicate. - // //if (TranslateLambda(provider, provider.Predicate) != provider.Predicate) - // // throw new Exception("AAAA!!!! Mappings went wrong!!!"); - // return provider; - //} - //else { - // // otherwise, there is a chance that new mappings should be applied to predicate - // var newPredicate = TranslateLambda(provider, provider.Predicate); - // return newPredicate == provider.Predicate - // ? provider - // : new FilterProvider(newSourceProvider, (Expression>) newPredicate); - //} } protected override Provider VisitJoin(JoinProvider provider)