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
Firebird-net-provider@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/firebird-net-provider

Reply via email to