diff --git a/src/EntityFramework.MicrosoftSqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs b/src/EntityFramework.MicrosoftSqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs index 1b8e58e05e0..de9f6f4504f 100644 --- a/src/EntityFramework.MicrosoftSqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EntityFramework.MicrosoftSqlServer/Query/Sql/Internal/SqlServerQuerySqlGenerator.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using JetBrains.Annotations; @@ -8,6 +9,7 @@ using Microsoft.Data.Entity.Query.Expressions.Internal; using Microsoft.Data.Entity.Storage; using Microsoft.Data.Entity.Utilities; +using Remotion.Linq.Parsing; namespace Microsoft.Data.Entity.Query.Sql.Internal { @@ -20,10 +22,10 @@ public SqlServerQuerySqlGenerator( [NotNull] IRelationalTypeMapper relationalTypeMapper, [NotNull] SelectExpression selectExpression) : base( - relationalCommandBuilderFactory, - sqlGenerationHelper, - parameterNameGeneratorFactory, - relationalTypeMapper, + relationalCommandBuilderFactory, + sqlGenerationHelper, + parameterNameGeneratorFactory, + relationalTypeMapper, selectExpression) { } @@ -69,6 +71,14 @@ protected override void GenerateLimitOffset(SelectExpression selectExpression) base.GenerateLimitOffset(selectExpression); } + protected override IReadOnlyList TransformProjection(SelectExpression selectExpression) + { + var comparisonTransformer = new ProjectionComparisonTransformingVisitor(); + return selectExpression.Projection + .Select(comparisonTransformer.Visit) + .ToList(); + } + public virtual Expression VisitRowNumber(RowNumberExpression rowNumberExpression) { Check.NotNull(rowNumberExpression, nameof(rowNumberExpression)); @@ -89,5 +99,59 @@ public override Expression VisitSqlFunction(SqlFunctionExpression sqlFunctionExp } return base.VisitSqlFunction(sqlFunctionExpression); } + + private class ProjectionComparisonTransformingVisitor : RelinqExpressionVisitor + { + protected override Expression VisitUnary(UnaryExpression node) + { + if (node.NodeType == ExpressionType.Not + && node.Operand is AliasExpression) + { + return Expression.Condition( + node, + Expression.Constant(true, typeof(bool)), + Expression.Constant(false, typeof(bool))); + } + + return base.VisitUnary(node); + } + + protected override Expression VisitBinary(BinaryExpression node) + { + if (node.IsComparisonOperation()) + { + return Expression.Condition( + node, + Expression.Constant(true, typeof(bool)), + Expression.Constant(false, typeof(bool))); + } + + return base.VisitBinary(node); + } + + + protected override Expression VisitConditional(ConditionalExpression node) + { + var test = Visit(node.Test); + if (test is AliasExpression) + { + return Expression.Condition( + Expression.Equal(test, Expression.Constant(true, typeof(bool))), + Visit(node.IfTrue), + Visit(node.IfFalse)); + } + + var condition = test as ConditionalExpression; + if (condition != null) + { + return Expression.Condition( + condition.Test, + Visit(node.IfTrue), + Visit(node.IfFalse)); + } + return base.VisitConditional(node); + } + } + } } diff --git a/src/EntityFramework.Relational/Extensions/ExpressionExtensions.cs b/src/EntityFramework.Relational/Extensions/ExpressionExtensions.cs index eea4ae203f0..5b76146b02d 100644 --- a/src/EntityFramework.Relational/Extensions/ExpressionExtensions.cs +++ b/src/EntityFramework.Relational/Extensions/ExpressionExtensions.cs @@ -19,6 +19,18 @@ public static bool IsLogicalOperation([NotNull] this Expression expression) || (expression.NodeType == ExpressionType.OrElse); } + public static bool IsComparisonOperation([NotNull] this Expression expression) + { + Check.NotNull(expression, nameof(expression)); + + return expression.NodeType == ExpressionType.Equal + || expression.NodeType == ExpressionType.NotEqual + || expression.NodeType == ExpressionType.LessThan + || expression.NodeType == ExpressionType.LessThanOrEqual + || expression.NodeType == ExpressionType.GreaterThan + || expression.NodeType == ExpressionType.GreaterThanOrEqual; + } + public static ColumnExpression TryGetColumnExpression([NotNull] this Expression expression) => (expression as AliasExpression)?.TryGetColumnExpression(); diff --git a/src/EntityFramework.Relational/Query/Sql/DefaultQuerySqlGenerator.cs b/src/EntityFramework.Relational/Query/Sql/DefaultQuerySqlGenerator.cs index 3bcb388e7e5..bee0a07afc5 100644 --- a/src/EntityFramework.Relational/Query/Sql/DefaultQuerySqlGenerator.cs +++ b/src/EntityFramework.Relational/Query/Sql/DefaultQuerySqlGenerator.cs @@ -136,6 +136,8 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) projectionAdded = true; } + var nullComparisonTransformer = new NullComparisonTransformingVisitor(_parametersValues); + if (selectExpression.Projection.Any()) { if (selectExpression.IsProjectStar) @@ -143,7 +145,10 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) _relationalCommandBuilder.Append(", "); } - VisitJoin(selectExpression.Projection); + var transformedProjection = TransformProjection(selectExpression) + .Select(e => nullComparisonTransformer.Visit(e)) + .ToList(); + VisitJoin(transformedProjection); projectionAdded = true; } @@ -175,7 +180,7 @@ public virtual Expression VisitSelect(SelectExpression selectExpression) else { var predicate - = new NullComparisonTransformingVisitor(_parametersValues) + = nullComparisonTransformer .Visit(selectExpression.Predicate); var relationalNullsOptimizedExpandingVisitor = new RelationalNullsOptimizedExpandingVisitor(); @@ -227,6 +232,8 @@ var predicate return selectExpression; } + protected virtual IReadOnlyList TransformProjection([NotNull] SelectExpression selectExpression) => selectExpression.Projection; + protected virtual void GenerateOrderBy([NotNull] IReadOnlyList orderings) { _relationalCommandBuilder.Append("ORDER BY "); @@ -1125,7 +1132,7 @@ protected virtual bool TryGenerateBinaryOperator(ExpressionType op, [NotNull] ou protected override Exception CreateUnhandledItemException(T unhandledItem, string visitMethod) => new NotImplementedException(visitMethod); - private class NullComparisonTransformingVisitor : RelinqExpressionVisitor + protected class NullComparisonTransformingVisitor : RelinqExpressionVisitor { private readonly IReadOnlyDictionary _parameterValues; @@ -1169,4 +1176,4 @@ var columnExpression } } } -} +} \ No newline at end of file diff --git a/test/EntityFramework.Core.FunctionalTests/GearsOfWarQueryTestBase.cs b/test/EntityFramework.Core.FunctionalTests/GearsOfWarQueryTestBase.cs index ffb78a130f6..c5052fd16e2 100644 --- a/test/EntityFramework.Core.FunctionalTests/GearsOfWarQueryTestBase.cs +++ b/test/EntityFramework.Core.FunctionalTests/GearsOfWarQueryTestBase.cs @@ -502,6 +502,72 @@ public virtual void Where_nullable_enum_with_nullable_parameter() } } + [ConditionalFact] + public virtual void Select_inverted_boolean() + { + using (var context = CreateContext()) + { + var automaticWeapons = context.Weapons + .Where(w => w.IsAutomatic) + .Select(w => new { w.Id, Manual = !w.IsAutomatic }) + .ToList(); + + Assert.True(automaticWeapons.All(t => t.Manual == false)); + } + } + + [ConditionalFact] + public virtual void Select_comparison_with_null() + { + AmmunitionType? ammunitionType = AmmunitionType.Cartridge; + using (var context = CreateContext()) + { + var cartidgeWeapons = context.Weapons + .Where(w => w.AmmunitionType == ammunitionType) + .Select(w => new { w.Id, Cartidge = w.AmmunitionType == ammunitionType }) + .ToList(); + + Assert.True(cartidgeWeapons.All(t => t.Cartidge == true)); + } + + ammunitionType = null; + using (var context = CreateContext()) + { + var cartidgeWeapons = context.Weapons + .Where(w => w.AmmunitionType == ammunitionType) + .Select(w => new { w.Id, Cartidge = w.AmmunitionType == ammunitionType }) + .ToList(); + + Assert.True(cartidgeWeapons.All(t => t.Cartidge == true)); + } + } + + [ConditionalFact] + public virtual void Select_ternary_operation_with_boolean() + { + using (var context = CreateContext()) + { + var weapons = context.Weapons + .Select(w => new { w.Id, Num = w.IsAutomatic ? 1 : 0}) + .ToList(); + + Assert.Equal(3, weapons.Count(w => w.Num == 1)); + } + } + + [ConditionalFact] + public virtual void Select_ternary_operation_with_inverted_boolean() + { + using (var context = CreateContext()) + { + var weapons = context.Weapons + .Select(w => new { w.Id, Num = !w.IsAutomatic ? 1 : 0 }) + .ToList(); + + Assert.Equal(7, weapons.Count(w => w.Num == 1)); + } + } + [ConditionalFact] public virtual void Select_Where_Navigation() { diff --git a/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/GearsOfWarModelInitializer.cs b/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/GearsOfWarModelInitializer.cs index 8693ec367b5..5f385da5809 100644 --- a/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/GearsOfWarModelInitializer.cs +++ b/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/GearsOfWarModelInitializer.cs @@ -66,61 +66,71 @@ public static void Seed(GearsOfWarContext context) var marcusLancer = new Weapon { Name = "Marcus' Lancer", - AmmunitionType = AmmunitionType.Cartridge + AmmunitionType = AmmunitionType.Cartridge, + IsAutomatic = true }; var marcusGnasher = new Weapon { Name = "Marcus' Gnasher", AmmunitionType = AmmunitionType.Shell, + IsAutomatic = false, SynergyWith = marcusLancer }; var domsHammerburst = new Weapon { Name = "Dom's Hammerburst", - AmmunitionType = AmmunitionType.Cartridge + AmmunitionType = AmmunitionType.Cartridge, + IsAutomatic = false }; var domsGnasher = new Weapon { Name = "Dom's Gnasher", - AmmunitionType = AmmunitionType.Shell + AmmunitionType = AmmunitionType.Shell, + IsAutomatic = false }; var colesGnasher = new Weapon { Name = "Cole's Gnasher", - AmmunitionType = AmmunitionType.Shell + AmmunitionType = AmmunitionType.Shell, + IsAutomatic = false }; var colesMulcher = new Weapon { Name = "Cole's Mulcher", - AmmunitionType = AmmunitionType.Cartridge + AmmunitionType = AmmunitionType.Cartridge, + IsAutomatic = true }; var bairdsLancer = new Weapon { Name = "Baird's Lancer", - AmmunitionType = AmmunitionType.Cartridge + AmmunitionType = AmmunitionType.Cartridge, + IsAutomatic = true }; var bairdsGnasher = new Weapon { Name = "Baird's Gnasher", - AmmunitionType = AmmunitionType.Shell + AmmunitionType = AmmunitionType.Shell, + IsAutomatic = false }; var paduksMarkza = new Weapon { Name = "Paduk's Markza", - AmmunitionType = AmmunitionType.Cartridge + AmmunitionType = AmmunitionType.Cartridge, + IsAutomatic = false }; var maulersFlail = new Weapon { - Name = "Mauler's Flail" + Name = "Mauler's Flail", + IsAutomatic = false }; context.Weapons.Add(marcusLancer); @@ -228,7 +238,7 @@ public static void Seed(GearsOfWarContext context) }; var marcus = new Officer - { + { Nickname = "Marcus", FullName = "Marcus Fenix", SquadId = deltaSquad.Id, diff --git a/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/Weapon.cs b/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/Weapon.cs index 12df1ef2de1..62191c19c8f 100644 --- a/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/Weapon.cs +++ b/test/EntityFramework.Core.FunctionalTests/TestModels/GearsOfWarModel/Weapon.cs @@ -9,6 +9,7 @@ public class Weapon public int Id { get; set; } public string Name { get; set; } public AmmunitionType? AmmunitionType { get; set; } + public bool IsAutomatic { get; set; } // 1 - 1 self reference public int? SynergyWithId { get; set; } diff --git a/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarFromSqlQuerySqlServerTest.cs b/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarFromSqlQuerySqlServerTest.cs index 2ad9fcce53a..5c3fbe724ff 100644 --- a/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarFromSqlQuerySqlServerTest.cs +++ b/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarFromSqlQuerySqlServerTest.cs @@ -14,7 +14,7 @@ public override void From_sql_queryable_simple_columns_out_of_order() base.From_sql_queryable_simple_columns_out_of_order(); Assert.Equal( - @"SELECT ""Id"", ""Name"", ""AmmunitionType"", ""OwnerFullName"", ""SynergyWithId"" FROM ""Weapon"" ORDER BY ""Name""", + @"SELECT ""Id"", ""Name"", ""IsAutomatic"", ""AmmunitionType"", ""OwnerFullName"", ""SynergyWithId"" FROM ""Weapon"" ORDER BY ""Name""", Sql); } diff --git a/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs b/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs index c1909176fa4..f3f4807c550 100644 --- a/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs +++ b/test/EntityFramework.MicrosoftSqlServer.FunctionalTests/GearsOfWarQuerySqlServerTest.cs @@ -23,7 +23,7 @@ FROM [Gear] AS [g] ) AS [g] ON ([t].[GearNickName] = [g].[Nickname]) AND ([t].[GearSquadId] = [g].[SquadId]) ORDER BY [g].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [g].[FullName] @@ -52,7 +52,7 @@ FROM [Gear] AS [g] ) AS [g] ON ([t].[GearNickName] = [g].[Nickname]) AND ([t].[GearSquadId] = [g].[SquadId]) ORDER BY [g].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [g].[FullName] @@ -171,7 +171,7 @@ FROM [Gear] AS [g] WHERE [g].[Discriminator] IN ('Officer', 'Gear') AND ([g].[Nickname] = 'Marcus') ORDER BY [g].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [g].[FullName] @@ -327,7 +327,7 @@ INNER JOIN [CogTag] AS [t] ON ([g].[SquadId] = [t].[GearSquadId]) AND ([g].[Nick WHERE [g].[Discriminator] IN ('Officer', 'Gear') ORDER BY [g].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [g].[FullName] @@ -349,7 +349,7 @@ FROM [CogTag] AS [t] INNER JOIN [Gear] AS [g] ON ([t].[GearSquadId] = [g].[SquadId]) AND ([t].[GearNickName] = [g].[Nickname]) ORDER BY [g].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [g].[FullName] @@ -416,7 +416,7 @@ FROM [Gear] AS [g] INNER JOIN [CogTag] AS [t] ON ([t0].[SquadId] = [t].[GearSquadId]) AND ([t0].[Nickname] = [t].[GearNickName]) ORDER BY [t0].[FullName] -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] INNER JOIN ( SELECT DISTINCT [t0].[FullName] @@ -466,7 +466,7 @@ public override void Include_with_nested_navigation_in_order_by() base.Include_with_nested_navigation_in_order_by(); Assert.Equal( - @"SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId], [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOrBirthName], [g].[Discriminator], [g].[FullName], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank] FROM [Weapon] AS [w] INNER JOIN [Gear] AS [w.Owner] ON [w].[OwnerFullName] = [w.Owner].[FullName] INNER JOIN [City] AS [w.Owner.CityOfBirth] ON [w.Owner].[CityOrBirthName] = [w.Owner.CityOfBirth].[Name] @@ -495,7 +495,7 @@ public override void Where_nullable_enum_with_constant() base.Where_nullable_enum_with_constant(); Assert.Equal( - @"SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] WHERE [w].[AmmunitionType] = 1", Sql); @@ -506,7 +506,7 @@ public override void Where_nullable_enum_with_null_constant() base.Where_nullable_enum_with_null_constant(); Assert.Equal( - @"SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] + @"SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] WHERE [w].[AmmunitionType] IS NULL", Sql); @@ -532,16 +532,80 @@ public override void Where_nullable_enum_with_nullable_parameter() Assert.Equal( @"@__ammunitionType_0: Cartridge -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] WHERE [w].[AmmunitionType] = @__ammunitionType_0 -SELECT [w].[Id], [w].[AmmunitionType], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] +SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId] FROM [Weapon] AS [w] WHERE [w].[AmmunitionType] IS NULL", Sql); } + public override void Select_inverted_boolean() + { + base.Select_inverted_boolean(); + + Assert.Equal( + @"SELECT [w].[Id], CASE + WHEN [w].[IsAutomatic] = 0 + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END +FROM [Weapon] AS [w] +WHERE [w].[IsAutomatic] = 1", + Sql); + } + + public override void Select_comparison_with_null() + { + base.Select_comparison_with_null(); + + Assert.Equal( + @"@__ammunitionType_1: Cartridge +@__ammunitionType_0: Cartridge + +SELECT [w].[Id], CASE + WHEN [w].[AmmunitionType] = @__ammunitionType_1 + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END +FROM [Weapon] AS [w] +WHERE [w].[AmmunitionType] = @__ammunitionType_0 + +SELECT [w].[Id], CASE + WHEN [w].[AmmunitionType] IS NULL + THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) +END +FROM [Weapon] AS [w] +WHERE [w].[AmmunitionType] IS NULL", + Sql); + } + + public override void Select_ternary_operation_with_boolean() + { + base.Select_ternary_operation_with_boolean(); + + Assert.Equal( + @"SELECT [w].[Id], CASE + WHEN [w].[IsAutomatic] = 1 + THEN 1 ELSE 0 +END +FROM [Weapon] AS [w]", + Sql); + } + + public override void Select_ternary_operation_with_inverted_boolean() + { + base.Select_ternary_operation_with_inverted_boolean(); + + Assert.Equal( + @"SELECT [w].[Id], CASE + WHEN [w].[IsAutomatic] = 0 + THEN 1 ELSE 0 +END +FROM [Weapon] AS [w]", + Sql); + } + public override void Select_Where_Navigation_Scalar_Equals_Navigation_Scalar() { base.Select_Where_Navigation_Scalar_Equals_Navigation_Scalar(); diff --git a/test/EntityFramework.Relational.FunctionalTests/GearsOfWarFromSqlQueryTestBase.cs b/test/EntityFramework.Relational.FunctionalTests/GearsOfWarFromSqlQueryTestBase.cs index ec4fb90aa9e..ab8ab074812 100644 --- a/test/EntityFramework.Relational.FunctionalTests/GearsOfWarFromSqlQueryTestBase.cs +++ b/test/EntityFramework.Relational.FunctionalTests/GearsOfWarFromSqlQueryTestBase.cs @@ -19,7 +19,7 @@ public virtual void From_sql_queryable_simple_columns_out_of_order() using (var context = CreateContext()) { var actual = context.Set() - .FromSql(@"SELECT ""Id"", ""Name"", ""AmmunitionType"", ""OwnerFullName"", ""SynergyWithId"" FROM ""Weapon"" ORDER BY ""Name""") + .FromSql(@"SELECT ""Id"", ""Name"", ""IsAutomatic"", ""AmmunitionType"", ""OwnerFullName"", ""SynergyWithId"" FROM ""Weapon"" ORDER BY ""Name""") .ToArray(); Assert.Equal(10, actual.Length);