Reintroduce legacy support to emulate row skipping in the most outer SELECT statement, by letting JetDataReader ignore returned rows.

pull/48/head
Lau 6 years ago
parent 933caa3130
commit 1f35fca138

@ -13,5 +13,6 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
{
string ConnectionString { get; }
DataAccessProviderType DataAccessProviderType { get; }
bool UseOuterSelectSkipEmulationViaDataReader { get; }
}
}

@ -22,6 +22,7 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
// private bool? _rowNumberPaging;
private DbProviderFactory _dataAccessProviderFactory;
private bool _useOuterSelectSkipEmulationViaDataReader;
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@ -46,6 +47,7 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
{
// _rowNumberPaging = copyFrom._rowNumberPaging;
_dataAccessProviderFactory = copyFrom._dataAccessProviderFactory;
_useOuterSelectSkipEmulationViaDataReader = copyFrom._useOuterSelectSkipEmulationViaDataReader;
}
/// <summary>
@ -114,6 +116,29 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
return clone;
}
/// <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 bool UseOuterSelectSkipEmulationViaDataReader => _useOuterSelectSkipEmulationViaDataReader;
/// <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 JetOptionsExtension WithUseOuterSelectSkipEmulationViaDataReader(bool enabled)
{
var clone = (JetOptionsExtension) Clone();
clone._useOuterSelectSkipEmulationViaDataReader = enabled;
return clone;
}
/// <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
@ -160,6 +185,11 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
builder.Append("DataAccessProviderFactory ");
}
if (Extension._useOuterSelectSkipEmulationViaDataReader)
{
builder.Append("UseOuterSelectSkipEmulationViaDataReader ");
}
_logFragment = builder.ToString();
}
@ -172,7 +202,8 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
if (_serviceProviderHash == null)
{
_serviceProviderHash = (base.GetServiceProviderHashCode() * 397) ^
(Extension._dataAccessProviderFactory?.GetHashCode() ?? 0L) /* ^
(Extension._dataAccessProviderFactory?.GetHashCode() ?? 0L) ^
(Extension._useOuterSelectSkipEmulationViaDataReader.GetHashCode() * 397) /* ^
(Extension._rowNumberPaging?.GetHashCode() ?? 0L)*/;
}
@ -185,8 +216,12 @@ namespace EntityFrameworkCore.Jet.Infrastructure.Internal
debugInfo["Jet:" + nameof(JetDbContextOptionsBuilder.UseRowNumberForPaging)]
= (Extension._rowNumberPaging?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture);
*/
debugInfo["Jet:" + nameof(JetOptionsExtension.DataAccessProviderFactory)]
debugInfo["Jet:" + nameof(DataAccessProviderFactory)]
= (Extension._dataAccessProviderFactory?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture);
#pragma warning disable 618
debugInfo["Jet:" + nameof(JetDbContextOptionsBuilder.UseOuterSelectSkipEmulationViaDataReader)]
#pragma warning restore 618
= Extension._useOuterSelectSkipEmulationViaDataReader.GetHashCode().ToString(CultureInfo.InvariantCulture);
}
}
}

@ -40,6 +40,20 @@ namespace EntityFrameworkCore.Jet.Infrastructure
// public virtual JetDbContextOptionsBuilder UseRowNumberForPaging(bool useRowNumberForPaging = true)
// => WithOption(e => e.WithRowNumberPaging(useRowNumberForPaging));
/// <summary>
/// Jet/ACE doesn't natively support row skipping. When this option is enabled, row skipping will be
/// emulated in the most outer SELECT statement, by letting the JetDataReader ignore as many returned rows
/// as should have been skipped by the database.
/// This will only work when `JetCommand.ExecuteDataReader()` is beeing used to execute the `JetCommand`.
/// It is recommanded to not use this option, but to switch to client evaluation instead, by inserting a
/// call to either `AsEnumerable()`, `AsAsyncEnumerable()`, `ToList()`, or `ToListAsync()` and only then
/// to use `Skip()`. This will work in all cases and independent of the specific `JetCommand.Execute()`
/// method called.
/// </summary>
[Obsolete("This method exists for backward compatibility reasons only. Switch to client evaluation instead.")]
public virtual JetDbContextOptionsBuilder UseOuterSelectSkipEmulationViaDataReader(bool enabled = true)
=> WithOption(e => e.WithUseOuterSelectSkipEmulationViaDataReader(enabled));
/// <summary>
/// Configures the context to use the default retrying <see cref="IExecutionStrategy" />.
/// </summary>

@ -27,6 +27,7 @@ namespace EntityFrameworkCore.Jet.Internal
// RowNumberPagingEnabled = jetOptions.RowNumberPaging ?? false;
DataAccessProviderType = GetDataAccessProviderTypeFromOptions(jetOptions);
UseOuterSelectSkipEmulationViaDataReader = jetOptions.UseOuterSelectSkipEmulationViaDataReader;
ConnectionString = jetOptions.Connection?.ConnectionString ?? jetOptions.ConnectionString;
}
@ -55,6 +56,14 @@ namespace EntityFrameworkCore.Jet.Internal
nameof(JetOptionsExtension.DataAccessProviderFactory),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
if (UseOuterSelectSkipEmulationViaDataReader != jetOptions.UseOuterSelectSkipEmulationViaDataReader)
{
throw new InvalidOperationException(
CoreStrings.SingletonOptionChanged(
nameof(JetOptionsExtension.UseOuterSelectSkipEmulationViaDataReader),
nameof(DbContextOptionsBuilder.UseInternalServiceProvider)));
}
}
private static DataAccessProviderType GetDataAccessProviderTypeFromOptions(JetOptionsExtension jetOptions)
@ -108,6 +117,14 @@ namespace EntityFrameworkCore.Jet.Internal
/// </summary>
public virtual DataAccessProviderType DataAccessProviderType { get; private set; }
/// <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 bool UseOuterSelectSkipEmulationViaDataReader { get; private set; }
/// <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

@ -5,13 +5,12 @@ using System.Collections.Generic;
using System.Data.Jet;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using EntityFrameworkCore.Jet.Infrastructure.Internal;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
using EntityFrameworkCore.Jet.Utilities;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
@ -37,6 +36,7 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal
};
private readonly ITypeMappingSource _typeMappingSource;
private readonly IJetOptions _options;
private readonly JetSqlExpressionFactory _sqlExpressionFactory;
private readonly ISqlGenerationHelper _sqlGenerationHelper;
private CoreTypeMapping _boolTypeMapping;
@ -48,11 +48,13 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal
public JetQuerySqlGenerator(
[NotNull] QuerySqlGeneratorDependencies dependencies,
ISqlExpressionFactory sqlExpressionFactory,
ITypeMappingSource typeMappingSource)
ITypeMappingSource typeMappingSource,
IJetOptions options)
: base(dependencies)
{
_sqlExpressionFactory = (JetSqlExpressionFactory) sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
_options = options;
_sqlGenerationHelper = dependencies.SqlGenerationHelper;
_boolTypeMapping = _typeMappingSource.FindMapping(typeof(bool));
}
@ -350,9 +352,18 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal
Check.NotNull(selectExpression, nameof(selectExpression));
if (selectExpression.Offset != null)
{
if (_options.UseOuterSelectSkipEmulationViaDataReader)
{
Sql.Append("SKIP ");
Visit(selectExpression.Offset);
Sql.Append(" ");
}
else
{
throw new InvalidOperationException("Jet does not support skipping rows. Switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync() if needed.");
}
}
if (selectExpression.Limit != null)
{

@ -1,5 +1,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using EntityFrameworkCore.Jet.Infrastructure.Internal;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Storage;
@ -15,6 +16,7 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal
[NotNull] private readonly QuerySqlGeneratorDependencies _dependencies;
[NotNull] private readonly JetSqlExpressionFactory _sqlExpressionFactory;
[NotNull] private readonly ITypeMappingSource _typeMappingSource;
[NotNull] private readonly IJetOptions _options;
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
@ -23,14 +25,16 @@ namespace EntityFrameworkCore.Jet.Query.Sql.Internal
public JetQuerySqlGeneratorFactory(
[NotNull] QuerySqlGeneratorDependencies dependencies,
[NotNull] ISqlExpressionFactory sqlExpressionFactory,
[NotNull] ITypeMappingSource typeMappingSource)
[NotNull] ITypeMappingSource typeMappingSource,
[NotNull] IJetOptions options)
{
_dependencies = dependencies;
_sqlExpressionFactory = (JetSqlExpressionFactory)sqlExpressionFactory;
_typeMappingSource = typeMappingSource;
_options = options;
}
public virtual QuerySqlGenerator Create()
=> new JetQuerySqlGenerator(_dependencies, _sqlExpressionFactory, _typeMappingSource);
=> new JetQuerySqlGenerator(_dependencies, _sqlExpressionFactory, _typeMappingSource, _options);
}
}

@ -16,8 +16,12 @@ namespace System.Data.Jet
private readonly JetConnection _connection;
private JetTransaction _transaction;
private int _outerSelectSkipEmulationViaDataReaderSkipCount;
private static readonly Regex _createProcedureExpression = new Regex(@"^\s*create\s*procedure\b", RegexOptions.IgnoreCase);
private static readonly Regex _topParameterRegularExpression = new Regex(@"(?<=(?:^|\s)select\s+top\s+)(?:@\w+|\?)(?=\s)", RegexOptions.IgnoreCase);
private static readonly Regex _outerSelectTopValueRegularExpression = new Regex(@"(?<=^\s*select\s+top\s+)\d+(?=\s)", RegexOptions.IgnoreCase);
private static readonly Regex _outerSelectSkipValueOrParameterRegularExpression = new Regex(@"(?<=^\s*select)\s+skip\s+(?<SkipValueOrParameter>@\w+|\?|\d+)(?=\s)", RegexOptions.IgnoreCase);
private static readonly Regex _selectRowCountRegularExpression = new Regex(@"^\s*select\s*@@rowcount\s*;?\s*$", RegexOptions.IgnoreCase);
private static readonly Regex _ifStatementRegex = new Regex(@"^\s*if\s*(?<not>not)?\s*exists\s*\((?<sqlCheckCommand>.+)\)\s*then\s*(?<sqlCommand>.*)$", RegexOptions.IgnoreCase);
@ -204,10 +208,12 @@ namespace System.Data.Jet
if ((dataReader = TryGetDataReaderForSelectRowCount(InnerCommand.CommandText)) == null)
{
FixupGlobalVariables();
PrepareOuterSelectSkipEmulationViaDataReader();
InlineTopParameters();
ModifyOuterSelectTopValueForOuterSelectSkipEmulationViaDataReader();
FixParameters();
dataReader = new JetDataReader(InnerCommand.ExecuteReader(behavior));
dataReader = new JetDataReader(InnerCommand.ExecuteReader(behavior), _outerSelectSkipEmulationViaDataReaderSkipCount);
_connection.RowCount = dataReader.RecordsAffected;
}
@ -526,6 +532,64 @@ namespace System.Data.Jet
}
}
private void ModifyOuterSelectTopValueForOuterSelectSkipEmulationViaDataReader()
{
// We modify the TOP clause parameter of the outer most SELECT statement if a SKIP clause was also
// specified, because Jet does not support skipping records at all, but we can optionally emulate skipping
// behavior for the outer most SELECT statement by controlling how the records are being fetched in
// JetDataReader.
if (_outerSelectSkipEmulationViaDataReaderSkipCount > 0)
{
InnerCommand.CommandText = _outerSelectTopValueRegularExpression.Replace(
InnerCommand.CommandText,
match => (int.Parse(match.Value) + _outerSelectSkipEmulationViaDataReaderSkipCount).ToString());
}
}
private void PrepareOuterSelectSkipEmulationViaDataReader()
{
// We inline the SKIP clause parameter of the outer most SELECT statement, because Jet does not support
// skipping records at all, but we can optionally emulate skipping behavior for the outer most SELECT
// statement by controlling how the records are being fetched in JetDataReader.
var match = _outerSelectSkipValueOrParameterRegularExpression.Match(InnerCommand.CommandText);
if (!match.Success)
{
return;
}
var skipValueOrParameter = match.Groups["SkipValueOrParameter"];
if (IsParameter(skipValueOrParameter.Value))
{
var parameters = InnerCommand.Parameters.Cast<DbParameter>()
.ToList();
if (parameters.Count <= 0)
{
throw new InvalidOperationException($@"Cannot find ""{skipValueOrParameter.Value}"" parameter for SKIP clause.");
}
var parameter = ExtractParameter(InnerCommand.CommandText, match.Index, parameters);
_outerSelectSkipEmulationViaDataReaderSkipCount = Convert.ToInt32(parameter.Value);
InnerCommand.Parameters.Clear();
InnerCommand.Parameters.AddRange(parameters.ToArray());
}
else
{
_outerSelectSkipEmulationViaDataReaderSkipCount = int.Parse(skipValueOrParameter.Value);
}
InnerCommand.CommandText = InnerCommand.CommandText.Remove(match.Index, match.Length);
}
protected virtual bool IsParameter(string fragment)
=> fragment.Equals("?") ||
fragment.Length >= 2 && fragment[0] == '@' && fragment[1] != '@';
protected virtual DbParameter ExtractParameter(string commandText, int count, List<DbParameter> parameters)
{
var indices = GetParameterIndices(commandText.Substring(0, count));

@ -18,6 +18,16 @@ namespace System.Data.Jet
_wrappedDataReader = dataReader;
}
public JetDataReader(DbDataReader dataReader, int skipCount)
: this(dataReader)
{
var i = 0;
while (i < skipCount && _wrappedDataReader.Read())
{
i++;
}
}
private readonly DbDataReader _wrappedDataReader;
public override void Close()

Loading…
Cancel
Save