diff --git a/src/EFCore.Jet/Update/Internal/JetUpdateSqlGenerator.cs b/src/EFCore.Jet/Update/Internal/JetUpdateSqlGenerator.cs index 7c3ce57..2a8c668 100644 --- a/src/EFCore.Jet/Update/Internal/JetUpdateSqlGenerator.cs +++ b/src/EFCore.Jet/Update/Internal/JetUpdateSqlGenerator.cs @@ -1,8 +1,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Globalization; using System.Text; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Update; +using Microsoft.Extensions.DependencyInjection; namespace EntityFrameworkCore.Jet.Update.Internal { @@ -49,23 +52,9 @@ namespace EntityFrameworkCore.Jet.Update.Internal /// protected override void AppendRowsAffectedWhereCondition(StringBuilder commandStringBuilder, int expectedRowsAffected) { - // TODO: Implement translation of @@ROWCOUNT related queries into System.Data.Jet. - // Every the AffectedRecords of every NonQueryExecution needs to be saved per connection, - // so it can be replaced in later queries if needed. - // Other executions should set this saved value just to 0. - - // Jet does not support ROWCOUNT - // Here we really hope that ROWCOUNT is not required - // Actually, RecordsAffected is handled by JetModificationCommandBatch - commandStringBuilder - .Append("1 = 1"); - - /* - commandStringBuilder .Append("@@ROWCOUNT = ") .Append(expectedRowsAffected.ToString(CultureInfo.InvariantCulture)); - */ } /// diff --git a/src/System.Data.Jet/JetCommand.cs b/src/System.Data.Jet/JetCommand.cs index d1938a0..739cf77 100644 --- a/src/System.Data.Jet/JetCommand.cs +++ b/src/System.Data.Jet/JetCommand.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data.Common; using System.Data.Jet.JetStoreSchemaDefinition; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -204,9 +205,7 @@ namespace System.Data.Jet if ((dataReader = TryGetDataReaderForSelectRowCount(InnerCommand.CommandText)) == null) { - InnerCommand.CommandText = ParseIdentity(InnerCommand.CommandText); - InnerCommand.CommandText = ParseGuid(InnerCommand.CommandText); - + FixupGlobalVariables(); InlineTopParameters(); FixParameters(); @@ -263,9 +262,8 @@ namespace System.Data.Jet return _connection.RowCount; } - InnerCommand.CommandText = ParseIdentity(InnerCommand.CommandText); - InnerCommand.CommandText = ParseGuid(InnerCommand.CommandText); - + FixupGlobalVariables(); + if (!CheckExists(InnerCommand.CommandText, out var newCommandText)) return 0; @@ -451,25 +449,36 @@ namespace System.Data.Jet } } - private string ParseIdentity(string commandText) + protected virtual void FixupGlobalVariables() { - // TODO: Fix the following code, that does work only for common scenarios. Use state machine instead. - if (commandText.ToLower() - .Contains("@@identity")) - { - DbCommand command; - command = (DbCommand) ((ICloneable) InnerCommand).Clone(); - command.CommandText = "Select @@identity"; - var identity = command.ExecuteScalar(); - var iIdentity = Convert.ToInt32(identity); - LogHelper.ShowInfo("@@identity = {0}", iIdentity); - return Regex.Replace(commandText, "@@identity", iIdentity.ToString(Globalization.CultureInfo.InvariantCulture), RegexOptions.IgnoreCase); - } + var commandText = InnerCommand.CommandText; + + commandText = FixupIdentity(commandText); + commandText = FixupRowCount(commandText); + commandText = FixupGuid(commandText); - return commandText; + InnerCommand.CommandText = commandText; } + + protected virtual string FixupIdentity(string commandText) + => FixupGlobalVariablePlaceholder( + commandText, "@@identity", (outerCommand, placeholder) => + { + var command = (DbCommand) ((ICloneable) outerCommand.InnerCommand).Clone(); + command.CommandText = $"SELECT {placeholder}"; + command.Parameters.Clear(); + + var identityValue = Convert.ToInt32(command.ExecuteScalar()); + + LogHelper.ShowInfo($"{placeholder} = {identityValue}"); + + return identityValue; + }); - private string ParseGuid(string commandText) + protected virtual string FixupRowCount(string commandText) + => FixupGlobalVariablePlaceholder(commandText, "@@rowcount", (outerCommand, placeholder) => outerCommand._connection.RowCount); + + protected virtual string FixupGuid(string commandText) { // TODO: Fix the following code, that does work only for common scenarios. Use state machine instead. while (commandText.ToLower() @@ -489,6 +498,26 @@ namespace System.Data.Jet return commandText; } + protected virtual string FixupGlobalVariablePlaceholder(string commandText, string placeholder, Func valueFactory) + where T : struct + { + var parser = new JetCommandParser(commandText); + var globalVariableIndices = parser.GetStateIndices('$'); + var placeholderValue = new Lazy(() => valueFactory(this, placeholder)); + var newCommandText = new StringBuilder(commandText); + + foreach (var globalVariableIndex in globalVariableIndices) + { + if (commandText.IndexOf(placeholder, globalVariableIndex, placeholder.Length, StringComparison.OrdinalIgnoreCase) > -1) + { + newCommandText.Remove(globalVariableIndex, placeholder.Length); + newCommandText.Insert(globalVariableIndex, placeholderValue.Value); + } + } + + return newCommandText.ToString(); + } + private void InlineTopParameters() { // We inline all TOP clause parameters of all SELECT statements, because Jet does not support parameters @@ -518,11 +547,7 @@ namespace System.Data.Jet InnerCommand.Parameters.AddRange(parameters.ToArray()); } } - - protected virtual bool IsParameter(string fragment) - => fragment.StartsWith("@") || - fragment.Equals("?"); - + protected virtual DbParameter ExtractParameter(string commandText, int count, List parameters) { var indices = GetParameterIndices(commandText.Substring(0, count));