Came across a problem while working with DB2 from .Net Framework 4.8.
We have a Db2 for z/OS v12 database. Our database has fields with type "TIMESTAMP WITH TIME ZONE". When using ADO.NET, these fields in models are of type DateTimeOffset. When getting data from the database, there are no problems, but when saving, we get the following error:
2022-12-27 08:16:09.2273 FATAL InputManager.Program System.Data.Entity.Infrastructure.DbUpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.UpdateException: An error occurred while updating the entries. See the inner exception for details. ---> System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.InvalidOperationException: Unknown PrimitiveTypeKind DateTimeOffset
at IBM.Data.DB2.EntityFramework.MetadataHelpers.GetDbType(PrimitiveTypeKind primitiveType, TypeUsage type)
at IBM.Data.DB2.EntityFramework.DmlSqlGenerator.ExpressionTranslator.CreateParameter(Object value, TypeUsage type)
at IBM.Data.DB2.EntityFramework.DmlSqlGenerator.ExpressionTranslator.Visit(DbConstantExpression expression)
at System.Data.Entity.Core.Common.CommandTrees.DbConstantExpression.Accept(DbExpressionVisitor visitor)
at IBM.Data.DB2.EntityFramework.DmlSqlGenerator.GenerateUpdateSql(DbUpdateCommandTree tree, SqlGenerator sqlGenerator, List`1& parameters, Boolean createParameters)
at IBM.Data.DB2.EntityFramework.SqlGenerator.GenerateSql(DbCommandTree tree, DB2ProviderManifest manifest, List`1& parameters, CommandType& commandType, Dictionary`2& overrideColumnReturnTypes, List`1& replaceParametersWithValues, String[]& rowsToSkipAndLimit)
at IBM.Data.DB2.EntityFramework.DB2ProviderServices.CreateCommand(DbProviderManifest manifest, DbCommandTree commandTree)
at IBM.Data.DB2.EntityFramework.DB2ProviderServices.CreateDbCommandDefinition(DbProviderManifest manifest, DbCommandTree commandTree)
at System.Data.Entity.Core.Common.DbProviderServices.CreateDbCommandDefinition(DbProviderManifest providerManifest, DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Common.DbProviderServices.CreateCommandDefinition(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Common.DbProviderServices.CreateCommand(DbCommandTree commandTree, DbInterceptionContext interceptionContext)
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.CreateCommand(DbModificationCommandTree commandTree)
--- End of inner exception stack trace ---
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.CreateCommand(DbModificationCommandTree commandTree)
at System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.CreateCommand(Dictionary`2 identifierValues)
at System.Data.Entity.Core.Mapping.Update.Internal.DynamicUpdateCommand.Execute(Dictionary`2 identifierValues, List`1 generatedValues)
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
--- End of inner exception stack trace ---
at System.Data.Entity.Core.Mapping.Update.Internal.UpdateTranslator.Update()
at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.<>c.<Update>b__21_0(UpdateTranslator ut)
at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update[T](T noChangesResult, Func`2 updateFunction)
at System.Data.Entity.Core.EntityClient.Internal.EntityAdapter.Update()
at System.Data.Entity.Core.Objects.ObjectContext.<SaveChangesToStore>b__153_0()
at System.Data.Entity.Core.Objects.ObjectContext.ExecuteInTransaction[T](Func`1 func, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction, Boolean releaseConnectionOnSuccess)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesToStore(SaveOptions options, IDbExecutionStrategy executionStrategy, Boolean startLocalTransaction)
at System.Data.Entity.Core.Objects.ObjectContext.<>c__DisplayClass148_0.<SaveChangesInternal>b__0()
at IBM.Data.DB2.EntityFramework.DB2DefaultSqlExecutionStrategy.Execute[TResult](Func`1 operation)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChangesInternal(SaveOptions options, Boolean executeInExistingTransaction)
at System.Data.Entity.Core.Objects.ObjectContext.SaveChanges(SaveOptions options)
at System.Data.Entity.Internal.InternalContext.SaveChanges()
--- End of inner exception stack trace ---
at System.Data.Entity.Internal.InternalContext.SaveChanges()
at System.Data.Entity.Internal.LazyInternalContext.SaveChanges()
at System.Data.Entity.DbContext.SaveChanges()
NuGet Packages installed: EntityFramework v6.4.4; EntityFramework.IBM.DB2 6.4.1. Although the same error occurred on other versions.
Code example:
//Model from ADO.NET
[Table("MyModel")]
public partial class MyModel
{
[Key]
[Column(TypeName = "integer")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int IdMyModel { get; set; }
[Column(TypeName = "timestz")]
public DateTimeOffset TmCreate { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyModel>()
.Property(e => e.TmCreate)
.HasColumnType("timestz");
}
//How it is used
var myModel = db.MyModel.FirstOrDefault(m => m.IdMyModel == 123); //OK
myModel.TmCreate = DateTimeOffset.Now;
db.SaveChanges(); //Error
Sql-injection works OK.
string dateFormat = DateTimeOffset.Now.ToString("o");
string query = $"UPDATE MyDB.\"MyModel\" set \"TmCreate\" = TIMESTAMP '{dateFormat}' where \"IdMyModel \" = 123;";
db.ExecuteSqlCommand(query);
I saw the table, but, as I understand it, it is for the version for windows, not for z/OS. Is it even possible to work with DateTimeOffset in our situation? What are we doing wrong?
Information that may be useful: When manually replacing DateTimeOffset with DateTime and timestz with timestmp, an attempt to read information causes an error, but it saves normally using the time zone from the system settings.
//Model from ADO.NET
[Table("MyModel")]
public partial class MyModel
{
[Key]
[Column(TypeName = "integer")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int IdMyModel { get; set; }
[Column(TypeName = "timestmp")]
public DateTime TmCreate { get; set; }
}
//How it is used
var myModel = db.MyModel.FirstOrDefault(m => m.IdMyModel == 123);//Erore
myModel.TmCreate = DateTime.Now;
db.SaveChanges(); //OK