Skip to content

Commit 6693512

Browse files
committed
Query: Throw exception for null key value in non-tracking
Resolves #26310
1 parent e28fe99 commit 6693512

File tree

2 files changed

+74
-9
lines changed

2 files changed

+74
-9
lines changed

Diff for: src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs

+55-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Reflection;
99
using System.Threading;
1010
using System.Threading.Tasks;
11+
using JetBrains.Annotations;
1112
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
1213
using Microsoft.EntityFrameworkCore.Diagnostics;
1314
using Microsoft.EntityFrameworkCore.Infrastructure;
@@ -322,6 +323,9 @@ private static readonly MethodInfo _startTrackingMethodInfo
322323
= typeof(QueryContext).GetRequiredMethod(
323324
nameof(QueryContext.StartTracking), typeof(IEntityType), typeof(object), typeof(ValueBuffer));
324325

326+
private static readonly MethodInfo _createNullKeyValueInNoTrackingQuery
327+
= typeof(EntityMaterializerInjectingExpressionVisitor).GetRequiredDeclaredMethod(nameof(CreateNullKeyValueInNoTrackingQuery));
328+
325329
private readonly IEntityMaterializerSource _entityMaterializerSource;
326330
private readonly QueryTrackingBehavior _queryTrackingBehavior;
327331
private readonly bool _queryStateMananger;
@@ -456,16 +460,38 @@ private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpres
456460
{
457461
if (primaryKey != null)
458462
{
459-
expressions.Add(
460-
Expression.IfThen(
463+
var keyValuesVariable = Expression.Variable(typeof(object[]), "keyValues" + _currentEntityIndex);
464+
variables.Add(keyValuesVariable);
465+
expressions.Add(Expression.Assign(
466+
keyValuesVariable,
467+
Expression.NewArrayInit(
468+
typeof(object),
461469
primaryKey.Properties.Select(
462-
p => Expression.NotEqual(
463-
valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p),
464-
Expression.Constant(null)))
465-
.Aggregate((a, b) => Expression.AndAlso(a, b)),
466-
MaterializeEntity(
467-
entityShaperExpression, materializationContextVariable, concreteEntityTypeVariable, instanceVariable,
468-
null)));
470+
p => valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p)))));
471+
var keyPropertiesCondition = primaryKey.Properties.Select(
472+
(p, i) => Expression.NotEqual(
473+
Expression.ArrayIndex(keyValuesVariable, Expression.Constant(i)),
474+
Expression.Constant(null)))
475+
.Aggregate((a, b) => Expression.AndAlso(a, b));
476+
477+
var entityMaterializationCode = MaterializeEntity(
478+
entityShaperExpression, materializationContextVariable, concreteEntityTypeVariable, instanceVariable, null);
479+
480+
if (entityShaperExpression.IsNullable)
481+
{
482+
expressions.Add(Expression.IfThen(keyPropertiesCondition, entityMaterializationCode));
483+
}
484+
else
485+
{
486+
expressions.Add(Expression.IfThenElse(
487+
keyPropertiesCondition,
488+
entityMaterializationCode,
489+
Expression.Call(
490+
_createNullKeyValueInNoTrackingQuery,
491+
Expression.Constant(entityType),
492+
Expression.Constant(primaryKey.Properties),
493+
keyValuesVariable)));
494+
}
469495
}
470496
else
471497
{
@@ -603,6 +629,26 @@ private BlockExpression CreateFullMaterializeExpression(
603629

604630
return Expression.Block(blockExpressions);
605631
}
632+
633+
[UsedImplicitly]
634+
private static Exception CreateNullKeyValueInNoTrackingQuery(
635+
IEntityType entityType, IReadOnlyList<IProperty> properties, object?[] keyValues)
636+
{
637+
var index = -1;
638+
for (var i = 0; i < keyValues.Length; i++)
639+
{
640+
if (keyValues[i] == null)
641+
{
642+
index = i;
643+
break;
644+
}
645+
}
646+
647+
var property = properties[index];
648+
649+
throw new InvalidOperationException(
650+
CoreStrings.InvalidKeyValue(entityType.DisplayName(), property.Name));
651+
}
606652
}
607653
}
608654
}

Diff for: test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs

+19
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Linq;
66
using System.Threading.Tasks;
7+
using Microsoft.EntityFrameworkCore.Diagnostics;
78
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
89
using Microsoft.EntityFrameworkCore.TestUtilities;
910
using Microsoft.EntityFrameworkCore.Utilities;
@@ -606,6 +607,24 @@ public virtual async Task FromSqlRaw_queryable_simple_projection_not_composed(bo
606607
WHERE (c[""Discriminator""] = ""Customer"")");
607608
}
608609

610+
[ConditionalTheory]
611+
[MemberData(nameof(IsAsyncData))]
612+
public async Task FromSqlRaw_queryable_simple_with_missing_key_and_non_tracking_throws(bool async)
613+
{
614+
using var context = CreateContext();
615+
var query = context.Set<Customer>()
616+
.FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Category""")
617+
.AsNoTracking();
618+
var exception = async
619+
? await Assert.ThrowsAsync<InvalidOperationException>(() => query.ToArrayAsync())
620+
: Assert.Throws<InvalidOperationException>(() => query.ToArray());
621+
622+
Assert.Equal(CoreStrings.InvalidKeyValue(
623+
context.Model.FindEntityType(typeof(Customer))!.DisplayName(),
624+
"CustomerID"),
625+
exception.Message);
626+
}
627+
609628
private void AssertSql(params string[] expected)
610629
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
611630

0 commit comments

Comments
 (0)