[FB-Tracker] Created: (DNET-738) EF does not detect concurrency update fail since FBClient returns row filled with null (storage generated) values instead of "nothing" (empty result)

classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view
|

[FB-Tracker] Created: (DNET-738) EF does not detect concurrency update fail since FBClient returns row filled with null (storage generated) values instead of "nothing" (empty result)

JIRA tracker@firebirdsql.org
EF does not detect concurrency update fail since FBClient returns row filled with null (storage generated) values instead of "nothing" (empty result)
-----------------------------------------------------------------------------------------------------------------------------------------------------

                 Key: DNET-738
                 URL: http://tracker.firebirdsql.org/browse/DNET-738
             Project: .NET Data provider
          Issue Type: Bug
          Components: ADO.NET Provider
    Affects Versions: 5.1.1.0
         Environment: FirebirdServer 2.5.4, .NET 4.5.1, EF 6.1.3
            Reporter: Jiri Fartak
            Assignee: Jiri Cincura


Synopsis:

FirebirdClient uses Execute block statements wrapping UPDATE command clause having RETURNING statement to return server-generated values (e.g. identity column, version...etc) back to EF.

This is typical example of such command:
-----
EXECUTE BLOCK (
p0 TIMESTAMP = @p0, p1 VARCHAR(16) CHARACTER SET UTF8 = @p1, p2 BIGINT = @p2, p3 CHAR(16) CHARACTER SET OCTETS = @p3, p4 BIGINT = @p4, p5 INT = @p5
) RETURNS (
"created" TIMESTAMP, "creator" VARCHAR(16), "deleted" TIMESTAMP, "deletor" VARCHAR(16), "version" INT)

AS BEGIN

UPDATE "PersistentObject"
SET "createdByOid" = NULL, "modified" = :p0, "modifier" = :p1, "modifiedByOid" = :p2, "deletedByOid" = NULL, "clsid" = :p3
WHERE (("oid" = :p4) AND ("version" = :p5))
RETURNING "created", "creator", "deleted", "deletor", "version" INTO :"created", :"creator", :"deleted", :"deletor", :"version";

SUSPEND;
END

------

The problem arises when UPDATE fails - when no row is updated due to concurrency, since no rows satisfied WHERE constraints (version has changed). If so, the following SUSPEND directive returns the row having ALL columns filled with null values to the caller.

This is misleading to EF (control layer), beacuse the presence of the row (albait with null values) causes, that command issued by EF will read this row (via FBDataReader()) as valid row (and so rowsAffected is 1 and not zero) and EF will then try to update entity's properties with these server-ganerated values (calls translator.BackPropagateServerGen()). However, since the row is having only null values, then properties that require value (.Required())  will make the EF to throw exception due to inconsistency (see below).

This is excerpt of control in System\Data\Mapping\Update\Internal\UpdateTranslator.cs:

                  foreach (UpdateCommand command in orderedCommands)
                {
                    // Remember the data sources so that we can throw meaningful exception
                    source = command;

                !!!! rowsAffected will have value of 1 instead of 0, since command.Execute returned row with nulls and not empty result set

                    long rowsAffected = command.Execute(translator, connection, identifierValues, generatedValues);

                !!! The line below would throw DbConcurrencyException (as we want and expect) if rowsAffected would be zero, this does not happen

                    translator.ValidateRowsAffected(rowsAffected, source);
                }

!! Following method throws the exception informing about inconsistency in server generated value and requirements for property in model
  translator.BackPropagateServerGen(generatedValues);



The FBClient behavior makes EF to throw this exception - informing about inconsistency - even though the problem was caused by concurrency update:

A null store-generated value was returned for a non-nullable member 'Created' of type 'WMS.Altair.Service.Repository.PersistentObjectModel'.
A null store-generated value was returned for a non-nullable member 'Created' of type 'WMS.Altair.Service.Repository.PersistentObjectModel'.
Výpis: Vnitřní výpis chyby:--->Typ chyby: DbUpdateExceptionZpráva: A null store-generated value was returned for a non-nullable member 'Created' of type'WMS.Altair.Service.Repository.PersistentObjectModel'.Výpis: v WMS.Altair.Service.Repository.IPAddressManager.Update(IPAddressModel model) v WMS.Altair.Web.Controllers.IPAddressController.Edit(IPAddressEditViewModel viewModel, StoreObjectState objectStates)Vnitřní výpis chyby:--->Typ chyby: UpdateExceptionZpráva: A null store-generated value was returned for a non-nullable member 'Created' of type 'WMS.Altair.Service.Repository.PersistentObjectModel'.Výpis: v System.Data.Entity.Core.Mapping.Update.Internal.PropagatorResult.AlignReturnValue(Object value, EdmMember member) v System.Data.Entity.Core.Mapping.Update.Internal.PropagatorResult.SetServerGenValue(Object value) v System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.BackPropagateServerGen(List`1 generatedValues) v System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update() v System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.b__2(UpdateTranslator ut) v System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction) v System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update() v System.Data.Entity.Core.Objects.ObjectContext.b__35() v System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) v System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction) v System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass2a.b__27() v System.Data.Entity.Infrastructure.DefaultExecutionStrategy.Execute[TResult](Func`1 operation) v System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction) v System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options) v System.Data.Entity.Internal.InternalContext.SaveChanges()


However, this exception would be throwed when FBClient would return empty result to EF:

--->
Typ chyby: DbUpdateConcurrencyException
Zpráva: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
Výpis: v WMS.Altair.Service.Repository.IPAddressManager.Update(IPAddressModel model) v C:\Users\genx.INTRANET\Documents\Visual Studio 2013\Projects\AltairWebClient\AltairServices\Repository\UseCaseManager\IPAddressManager\IPAddressManager.cs:řádek 661 v WMS.Altair.Web.Controllers.IPAddressController.Edit(IPAddressEditViewModel viewModel, StoreObjectState objectStates) v C:\Users\genx.INTRANET\Documents\Visual Studio 2013\Projects\AltairWebClient\AltairWebClient\Controllers\IPAddressController.cs:řádek 485
Vnitřní výpis chyby:
--->
Typ chyby: OptimisticConcurrencyException
Zpráva: Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
Výpis: v System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.ValidateRowsAffected(Int64 rowsAffected, UpdateCommand source) v System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update() v System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess) v System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction) v System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction) v System.Data.Entity.Internal.InternalContext.SaveChanges()



----

Suggested Fix:

Instead of simple unconditional calling of SUSPEND we made minor change to the DmlSqlGenerator.GenerateReturningSql() method:

...
if (row_count > 0) then SUSPEND;
commandText.AppendLine("END");

...

if UPDATE command in EXECUTE BLOCK succeeded then Firebird Server sets row_count to 1 and then we sent the row to the caller, otherwise we do nothing (caller obtains empty result set). According Firebird database manual, the row_count should be supported since FB 1.5+ and it should not be too limiting for today deployments.

EF then gets empty result set and correctly detects it as DbConecurrencyException. We did minor testing and it looks promising.

Affected provider versions (where seen): 4.7.0.0, 5.1.1.0 and maybe others if GenerateReturningSql() generates the same pattern.

Jiri Fartak, WMS s.r.o.


--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://tracker.firebirdsql.org/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

       

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, SlashDot.org! http://sdm.link/slashdot
_______________________________________________
Firebird-net-provider mailing list
[hidden email]
https://lists.sourceforge.net/lists/listinfo/firebird-net-provider