Skip to content

Commit

Permalink
Query: Throw exception for null key value in non-tracking
Browse files Browse the repository at this point in the history
Resolves #26310
  • Loading branch information
smitpatel committed Oct 11, 2021
1 parent e28fe99 commit 6693512
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 9 deletions.
64 changes: 55 additions & 9 deletions src/EFCore/Query/ShapedQueryCompilingExpressionVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
Expand Down Expand Up @@ -322,6 +323,9 @@ private static readonly MethodInfo _startTrackingMethodInfo
= typeof(QueryContext).GetRequiredMethod(
nameof(QueryContext.StartTracking), typeof(IEntityType), typeof(object), typeof(ValueBuffer));

private static readonly MethodInfo _createNullKeyValueInNoTrackingQuery
= typeof(EntityMaterializerInjectingExpressionVisitor).GetRequiredDeclaredMethod(nameof(CreateNullKeyValueInNoTrackingQuery));

private readonly IEntityMaterializerSource _entityMaterializerSource;
private readonly QueryTrackingBehavior _queryTrackingBehavior;
private readonly bool _queryStateMananger;
Expand Down Expand Up @@ -456,16 +460,38 @@ private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpres
{
if (primaryKey != null)
{
expressions.Add(
Expression.IfThen(
var keyValuesVariable = Expression.Variable(typeof(object[]), "keyValues" + _currentEntityIndex);
variables.Add(keyValuesVariable);
expressions.Add(Expression.Assign(
keyValuesVariable,
Expression.NewArrayInit(
typeof(object),
primaryKey.Properties.Select(
p => Expression.NotEqual(
valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p),
Expression.Constant(null)))
.Aggregate((a, b) => Expression.AndAlso(a, b)),
MaterializeEntity(
entityShaperExpression, materializationContextVariable, concreteEntityTypeVariable, instanceVariable,
null)));
p => valueBufferExpression.CreateValueBufferReadValueExpression(typeof(object), p.GetIndex(), p)))));
var keyPropertiesCondition = primaryKey.Properties.Select(
(p, i) => Expression.NotEqual(
Expression.ArrayIndex(keyValuesVariable, Expression.Constant(i)),
Expression.Constant(null)))
.Aggregate((a, b) => Expression.AndAlso(a, b));

var entityMaterializationCode = MaterializeEntity(
entityShaperExpression, materializationContextVariable, concreteEntityTypeVariable, instanceVariable, null);

if (entityShaperExpression.IsNullable)
{
expressions.Add(Expression.IfThen(keyPropertiesCondition, entityMaterializationCode));
}
else
{
expressions.Add(Expression.IfThenElse(
keyPropertiesCondition,
entityMaterializationCode,
Expression.Call(
_createNullKeyValueInNoTrackingQuery,
Expression.Constant(entityType),
Expression.Constant(primaryKey.Properties),
keyValuesVariable)));
}
}
else
{
Expand Down Expand Up @@ -603,6 +629,26 @@ private BlockExpression CreateFullMaterializeExpression(

return Expression.Block(blockExpressions);
}

[UsedImplicitly]
private static Exception CreateNullKeyValueInNoTrackingQuery(
IEntityType entityType, IReadOnlyList<IProperty> properties, object?[] keyValues)
{
var index = -1;
for (var i = 0; i < keyValues.Length; i++)
{
if (keyValues[i] == null)
{
index = i;
break;
}
}

var property = properties[index];

throw new InvalidOperationException(
CoreStrings.InvalidKeyValue(entityType.DisplayName(), property.Name));
}
}
}
}
19 changes: 19 additions & 0 deletions test/EFCore.Cosmos.FunctionalTests/Query/FromSqlQueryCosmosTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Microsoft.EntityFrameworkCore.Utilities;
Expand Down Expand Up @@ -606,6 +607,24 @@ public virtual async Task FromSqlRaw_queryable_simple_projection_not_composed(bo
WHERE (c[""Discriminator""] = ""Customer"")");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task FromSqlRaw_queryable_simple_with_missing_key_and_non_tracking_throws(bool async)
{
using var context = CreateContext();
var query = context.Set<Customer>()
.FromSqlRaw(@"SELECT * FROM root c WHERE c[""Discriminator""] = ""Category""")
.AsNoTracking();
var exception = async
? await Assert.ThrowsAsync<InvalidOperationException>(() => query.ToArrayAsync())
: Assert.Throws<InvalidOperationException>(() => query.ToArray());

Assert.Equal(CoreStrings.InvalidKeyValue(
context.Model.FindEntityType(typeof(Customer))!.DisplayName(),
"CustomerID"),
exception.Message);
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down

0 comments on commit 6693512

Please sign in to comment.