Skip to content

Commit

Permalink
Add EF.Functions.Random for SqlServer, Cosmos, Sqlite
Browse files Browse the repository at this point in the history
  • Loading branch information
HuyLuong committed Nov 3, 2020
1 parent d5a3ebc commit 6500bd2
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public CosmosMethodCallTranslatorProvider(
{
new EqualsTranslator(sqlExpressionFactory),
new StringMethodTranslator(sqlExpressionFactory),
new ContainsTranslator(sqlExpressionFactory)
new ContainsTranslator(sqlExpressionFactory),
new RandomTranslator(sqlExpressionFactory)
//new LikeTranslator(sqlExpressionFactory),
//new EnumHasFlagTranslator(sqlExpressionFactory),
//new GetValueOrDefaultTranslator(sqlExpressionFactory),
Expand Down
59 changes: 59 additions & 0 deletions src/EFCore.Cosmos/Query/Internal/RandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class RandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public RandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public SqlServerMethodCallTranslatorProvider([NotNull] RelationalMethodCallTrans
new SqlServerMathTranslator(sqlExpressionFactory),
new SqlServerNewGuidTranslator(sqlExpressionFactory),
new SqlServerObjectToStringTranslator(sqlExpressionFactory),
new SqlServerStringMethodTranslator(sqlExpressionFactory)
new SqlServerStringMethodTranslator(sqlExpressionFactory),
new SqlServerRandomTranslator(sqlExpressionFactory)
});
}
}
Expand Down
63 changes: 63 additions & 0 deletions src/EFCore.SqlServer/Query/Internal/SqlServerRandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// 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;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqlServerRandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqlServerRandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"RAND",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public SqliteMethodCallTranslatorProvider([NotNull] RelationalMethodCallTranslat
new SqliteMathTranslator(sqlExpressionFactory),
new SqliteObjectToStringTranslator(sqlExpressionFactory),
new SqliteRegexMethodTranslator(sqlExpressionFactory),
new SqliteRandomTranslator(sqlExpressionFactory),
new SqliteStringMethodTranslator(sqlExpressionFactory)
});
}
Expand Down
73 changes: 73 additions & 0 deletions src/EFCore.Sqlite.Core/Query/Internal/SqliteRandomTranslator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Utilities;

namespace Microsoft.EntityFrameworkCore.Sqlite.Query.Internal
{
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public class SqliteRandomTranslator : IMethodCallTranslator
{
private static readonly MethodInfo _methodInfo = typeof(DbFunctionsExtensions).GetRuntimeMethod(nameof(DbFunctionsExtensions.Random), new[] { typeof(DbFunctions) });
private readonly ISqlExpressionFactory _sqlExpressionFactory;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public SqliteRandomTranslator([NotNull] ISqlExpressionFactory sqlExpressionFactory)
{
_sqlExpressionFactory = sqlExpressionFactory;
}

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual SqlExpression Translate(
SqlExpression instance,
MethodInfo method,
IReadOnlyList<SqlExpression> arguments,
IDiagnosticsLogger<DbLoggerCategory.Query> logger)
{
Check.NotNull(method, nameof(method));
Check.NotNull(arguments, nameof(arguments));
Check.NotNull(logger, nameof(logger));

return _methodInfo.Equals(method)
? _sqlExpressionFactory.Function(
"abs",
new SqlExpression[]
{
_sqlExpressionFactory.Divide(
_sqlExpressionFactory.Function(
"random",
Array.Empty<SqlExpression>(),
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType),
_sqlExpressionFactory.Constant(9223372036854775807.0))
},
nullable: false,
argumentsPropagateNullability: Array.Empty<bool>(),
method.ReturnType)
: null;
}
}
}
14 changes: 14 additions & 0 deletions src/EFCore/DbFunctionsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,21 @@ public static bool Like(
[CanBeNull] string escapeCharacter)
=> LikeCore(matchExpression, pattern, escapeCharacter);

/// <summary>
/// <para>
/// An implementation of the SQL RAND operation. On relational databases this is usually directly
/// translated to SQL.
/// </para>
/// </summary>
/// <param name="_">The DbFunctions instance.</param>
/// <returns>a random decimal number between 0 and 1.</returns>
public static decimal Random([CanBeNull] this DbFunctions _)
=> RandomCore();

private static bool LikeCore(string matchExpression, string pattern, string escapeCharacter)
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Like)));

private static decimal RandomCore()
=> throw new InvalidOperationException(CoreStrings.FunctionOnClient(nameof(Random)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.Threading.Tasks;
using Microsoft.EntityFrameworkCore.TestModels.Northwind;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;
using Xunit.Abstractions;

namespace Microsoft.EntityFrameworkCore.Query
{
public class NorthwindDbFunctionsQueryCosmosTest : QueryTestBase<NorthwindQueryCosmosFixture<NoopModelCustomizer>>
{
public NorthwindDbFunctionsQueryCosmosTest(
NorthwindQueryCosmosFixture<NoopModelCustomizer> fixture,
ITestOutputHelper testOutputHelper)
: base(fixture)
{
ClearLog();
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Random_return_less_than_1(bool async)
{
await AssertCount(
async,
ss => ss.Set<Order>(),
ss => ss.Set<Order>(),
ss => EF.Functions.Random() < 1,
c => true);

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() < 1.0))");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Random_return_greater_than_0(bool async)
{
await AssertCount(
async,
ss => ss.Set<Order>(),
ss => ss.Set<Order>(),
ss => EF.Functions.Random() >= 0,
c => true);

AssertSql(
@"SELECT COUNT(1) AS c
FROM root c
WHERE ((c[""Discriminator""] = ""Order"") AND (RAND() >= 0.0))");
}

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

protected void ClearLog()
=> Fixture.TestSqlLoggerFactory.Clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,40 @@ FROM [Orders] AS [o]
}
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Random_return_less_than_1(bool async)
{
await AssertCount(
async,
ss => ss.Set<Order>(),
ss => ss.Set<Order>(),
ss => EF.Functions.Random() < 1,
c => true);

AssertSql(
@"SELECT COUNT(*)
FROM [Orders] AS [o]
WHERE RAND() < 1.0");
}

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public async Task Random_return_greater_than_0(bool async)
{
await AssertCount(
async,
ss => ss.Set<Order>(),
ss => ss.Set<Order>(),
ss => EF.Functions.Random() >= 0,
c => true);

AssertSql(
@"SELECT COUNT(*)
FROM [Orders] AS [o]
WHERE RAND() >= 0.0");
}

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

0 comments on commit 6500bd2

Please sign in to comment.