diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 429e3aab..f7443b64 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -126,8 +126,8 @@ jobs:
- name: Run EntityFrameworkCore.Ydb.QuickStart
run: |
cd ./examples/EntityFrameworkCore.Ydb.QuickStart
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet run
@@ -135,8 +135,8 @@ jobs:
- name: Run EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial
run: |
cd ./examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
dotnet ef migrations add InitialCreate
dotnet ef database update
dotnet run
diff --git a/examples/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj b/examples/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj
index 0622e0c7..f4bc20e2 100644
--- a/examples/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj
+++ b/examples/EntityFrameworkCore.Ydb.QuickStart/EntityFrameworkCore.Ydb.QuickStart.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/examples/EntityFrameworkCore.Ydb.QuickStart/README.md b/examples/EntityFrameworkCore.Ydb.QuickStart/README.md
index 86b56ec9..511e723e 100644
--- a/examples/EntityFrameworkCore.Ydb.QuickStart/README.md
+++ b/examples/EntityFrameworkCore.Ydb.QuickStart/README.md
@@ -10,8 +10,8 @@ shows how to get started with EF, define a model, populate it with data and then
2. Create the database:
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
dotnet ef migrations add InitialCreate
dotnet ef database update
```
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/AddEntity.Sample.csproj b/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/AddEntity.Sample.csproj
index 5b482857..71d7a66c 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/AddEntity.Sample.csproj
+++ b/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/AddEntity.Sample.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/README.md b/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/README.md
index 4d600f18..f7796263 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/README.md
+++ b/examples/EntityFrameworkCore.Ydb.Samples/AddEntity.Sample/README.md
@@ -11,8 +11,8 @@ Based on the [tutorial](https://www.csharptutorial.net/entity-framework-core-tut
2. Install the EF Core CLI tool and dependencies (if needed):
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
```
3. Create the database and apply migrations:
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/Database.Operations.Tutorial.csproj b/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/Database.Operations.Tutorial.csproj
index e9e7ec20..1eabfe76 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/Database.Operations.Tutorial.csproj
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/Database.Operations.Tutorial.csproj
@@ -14,7 +14,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/README.md b/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/README.md
index 45a23410..42bceb3d 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/README.md
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Database.Operations.Tutorial/README.md
@@ -10,8 +10,8 @@ Based on the [tutorial](https://www.csharptutorial.net/entity-framework-core-tut
2. Install the EF Core CLI tool and dependencies (if needed):
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
```
3. Create the database and apply migrations:
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/README.md b/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/README.md
index b12291c7..71d5d18e 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/README.md
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/README.md
@@ -11,8 +11,8 @@ The focus here is to show what database schema (DDL) will be generated by EF Cor
2. Install the EF Core CLI tool (if needed):
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
```
3. Add a migration to generate the DDL (if not already present):
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/Schema.ManyToMany.csproj b/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/Schema.ManyToMany.csproj
index 4449f9bb..10471c41 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/Schema.ManyToMany.csproj
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.ManyToMany/Schema.ManyToMany.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/README.md b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/README.md
index f1a9d02f..456df5c8 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/README.md
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/README.md
@@ -10,8 +10,8 @@ The focus here is to show what database schema (DDL) will be generated by EF Cor
2. Install the EF Core CLI tool (if needed):
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
```
3. Add a migration to generate the DDL (if not already present):
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/Schema.OneToMany.csproj b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/Schema.OneToMany.csproj
index 29520a74..76057cce 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/Schema.OneToMany.csproj
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToMany/Schema.OneToMany.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/README.md b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/README.md
index 5609e498..bf444ab8 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/README.md
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/README.md
@@ -10,8 +10,8 @@ The focus here is to show what database schema (DDL) will be generated by EF Cor
2. Install the EF Core CLI tool (if needed):
```bash
- dotnet tool install --global dotnet-ef
- dotnet add package Microsoft.EntityFrameworkCore.Design
+ dotnet tool install --global dotnet-ef --version 9.0.10
+ dotnet add package Microsoft.EntityFrameworkCore.Design --version 9.0.10
```
3. Add a migration to generate the DDL (if not already present):
diff --git a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/Schema.OneToOne.csproj b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/Schema.OneToOne.csproj
index 484e670b..f915940e 100644
--- a/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/Schema.OneToOne.csproj
+++ b/examples/EntityFrameworkCore.Ydb.Samples/Schema.OneToOne/Schema.OneToOne.csproj
@@ -13,7 +13,7 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
diff --git a/src/EFCore.Ydb/CHANGELOG.md b/src/EFCore.Ydb/CHANGELOG.md
index 4966623c..3eca7298 100644
--- a/src/EFCore.Ydb/CHANGELOG.md
+++ b/src/EFCore.Ydb/CHANGELOG.md
@@ -1,3 +1,6 @@
+- Fixed bug: Missing/removed __EFMigrationsHistory caused INSERT errors (LockMigration); migrations now handle table recreation and locking properly ([#544](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/544)).
+- Added support for new YDB data types - `Date32`, `Datetime64`, `Timestamp64` and `Interval64`.
+- Fixed bug: Missing `DEFAULT` clause generation in `YdbMigrationsSqlGenerator` (handles defaultValue/defaultValueSql) ([#552](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/552)).
- Fixed bug: SqlQuery throws exception when using list parameters ([#540](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/540)).
- Added support for the YDB retry policy (ADO.NET) and new configuration methods in `YdbDbContextOptionsBuilder`:
- `EnableRetryIdempotence()`: enables retries for errors classified as idempotent. You must ensure the operation itself is idempotent.
diff --git a/src/EFCore.Ydb/src/Migrations/Internal/YdbHistoryRepository.cs b/src/EFCore.Ydb/src/Migrations/Internal/YdbHistoryRepository.cs
index a4de8c3e..f7b28dd1 100644
--- a/src/EFCore.Ydb/src/Migrations/Internal/YdbHistoryRepository.cs
+++ b/src/EFCore.Ydb/src/Migrations/Internal/YdbHistoryRepository.cs
@@ -45,7 +45,7 @@ public override async Task AcquireDatabaseLockAsync(
{
await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(
AcquireDatabaseLockCommand(),
- ((IYdbRelationalConnection)Dependencies.Connection).Clone(), // TODO usage ExecutionContext
+ Dependencies.Connection,
new MigrationExecutionState(),
commitTransaction: true,
cancellationToken: cancellationToken
@@ -85,9 +85,7 @@ private async Task ReleaseDatabaseLockAsync()
try
{
await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(
- ReleaseDatabaseLockCommand(),
- ((IYdbRelationalConnection)Dependencies.Connection).Clone()
- ).ConfigureAwait(false);
+ ReleaseDatabaseLockCommand(), Dependencies.Connection).ConfigureAwait(false);
return;
}
@@ -98,10 +96,8 @@ await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(
}
}
- private IReadOnlyList ReleaseDatabaseLockCommand() =>
- Dependencies.MigrationsSqlGenerator.Generate(new List
- { new SqlOperation { Sql = GetDeleteScript(LockKey) } }
- );
+ private IReadOnlyList ReleaseDatabaseLockCommand() => Dependencies.MigrationsSqlGenerator
+ .Generate(new List { new SqlOperation { Sql = GetDeleteScript(LockKey) } });
bool IHistoryRepository.CreateIfNotExists() => CreateIfNotExistsAsync().GetAwaiter().GetResult();
@@ -135,13 +131,7 @@ await Dependencies.MigrationCommandExecutor.ExecuteNonQueryAsync(
private IReadOnlyList GetCreateIfNotExistsCommands() =>
Dependencies.MigrationsSqlGenerator.Generate(new List
- {
- new SqlOperation
- {
- Sql = GetCreateIfNotExistsScript(),
- SuppressTransaction = true
- }
- });
+ { new SqlOperation { Sql = GetCreateIfNotExistsScript(), SuppressTransaction = true } });
public override string GetCreateIfNotExistsScript()
=> GetCreateScript().Replace("CREATE TABLE", "CREATE TABLE IF NOT EXISTS");
@@ -179,8 +169,8 @@ private IReadOnlyList SelectHistoryTableCommand() =>
{
new SqlOperation
{
- Sql = $"SELECT * FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)}" +
- $" WHERE '{MigrationIdColumnName}' = '{LockKey}';"
+ Sql = $"SELECT * FROM {SqlGenerationHelper.DelimitIdentifier(TableName, TableSchema)} " +
+ $"WHERE '{MigrationIdColumnName}' = CAST(RandomUuid(0) AS Text)"
}
});
diff --git a/src/EFCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs b/src/EFCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs
index 79c68fce..a3ed9bd1 100644
--- a/src/EFCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs
+++ b/src/EFCore.Ydb/src/Migrations/YdbMigrationsSqlGenerator.cs
@@ -81,6 +81,11 @@ MigrationCommandListBuilder builder
.Append(" ")
.Append(columnType)
.Append(operation.IsNullable ? string.Empty : " NOT NULL");
+
+ if (autoincrement == true)
+ return;
+
+ DefaultValue(operation.DefaultValue, operation.DefaultValueSql, columnType, builder);
}
protected override void CreateTablePrimaryKeyConstraint(
diff --git a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateOnlyTypeMapping.cs b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateOnlyTypeMapping.cs
index 5cbb8500..da4c5785 100644
--- a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateOnlyTypeMapping.cs
+++ b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateOnlyTypeMapping.cs
@@ -1,34 +1,21 @@
using System;
-using Microsoft.EntityFrameworkCore.Storage;
+using Ydb.Sdk.Ado.YdbType;
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
-// TODO: Await DateOnly support in Ydb.Sdk
-public class YdbDateOnlyTypeMapping : RelationalTypeMapping
+public class YdbDateOnlyTypeMapping : YdbTypeMapping
{
- private const string DateOnlyFormatConst = "{0:yyyy-MM-dd}";
-
- public YdbDateOnlyTypeMapping(string storeType)
- : base(
- new RelationalTypeMappingParameters(
- new CoreTypeMappingParameters(typeof(DateOnly)),
- storeType,
- StoreTypePostfix.None,
- System.Data.DbType.Date
- )
- )
+ public YdbDateOnlyTypeMapping(YdbDbType ydbDbType) : base(typeof(DateOnly), ydbDbType)
{
}
- protected YdbDateOnlyTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters)
+ private YdbDateOnlyTypeMapping(RelationalTypeMappingParameters parameters, YdbDbType ydbDbType)
+ : base(parameters, ydbDbType)
{
}
- protected override YdbDateOnlyTypeMapping Clone(RelationalTypeMappingParameters parameters) => new(parameters);
+ protected override YdbDateOnlyTypeMapping Clone(RelationalTypeMappingParameters parameters) =>
+ new(parameters, YdbDbType);
- protected override string GenerateNonNullSqlLiteral(object value)
- {
- var dateOnly = (DateOnly)value;
- return $"Date('{dateOnly.ToString(DateOnlyFormatConst)}')";
- }
+ protected override string SqlLiteralFormatString => $"{YdbDbType}('{{0:yyyy-MM-dd}}')";
}
diff --git a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateTimeTypeMapping.cs b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateTimeTypeMapping.cs
index 47a37960..59f1ad2e 100644
--- a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateTimeTypeMapping.cs
+++ b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDateTimeTypeMapping.cs
@@ -1,17 +1,27 @@
-using System.Data;
-using Microsoft.EntityFrameworkCore.Storage;
+using System;
+using Ydb.Sdk.Ado.YdbType;
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
-public class YdbDateTimeTypeMapping(
- string storeType,
- DbType? dbType
-) : DateTimeTypeMapping(storeType, dbType)
+public class YdbDateTimeTypeMapping : YdbTypeMapping
{
- private const string DateTimeFormatConst = @"{0:yyyy-MM-dd HH\:mm\:ss.fffffff}";
+ public YdbDateTimeTypeMapping(YdbDbType ydbDbType) : base(typeof(DateTime), ydbDbType)
+ {
+ }
- private string StoreTypeLiteral { get; } = storeType;
+ private YdbDateTimeTypeMapping(RelationalTypeMappingParameters parameters, YdbDbType ydbDbType)
+ : base(parameters, ydbDbType)
+ {
+ }
- protected override string SqlLiteralFormatString
- => "CAST('" + DateTimeFormatConst + $"' AS {StoreTypeLiteral})";
+ protected override YdbDateTimeTypeMapping Clone(RelationalTypeMappingParameters parameters) =>
+ new(parameters, YdbDbType);
+
+ protected override string SqlLiteralFormatString => YdbDbType switch
+ {
+ YdbDbType.Timestamp or YdbDbType.Timestamp64 => $@"{YdbDbType}('{{0:yyyy-MM-ddTHH\:mm\:ss.ffffffZ}}')",
+ YdbDbType.Datetime or YdbDbType.Datetime64 => $@"{YdbDbType}('{{0:yyyy-MM-ddTHH\:mm\:ssZ}}')",
+ YdbDbType.Date or YdbDbType.Date32 => $"{YdbDbType}('{{0:yyyy-MM-dd}}')",
+ _ => throw new ArgumentOutOfRangeException(nameof(YdbDbType), YdbDbType, null)
+ };
}
diff --git a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTimeSpanTypeMapping.cs b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTimeSpanTypeMapping.cs
new file mode 100644
index 00000000..de40022b
--- /dev/null
+++ b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTimeSpanTypeMapping.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Xml;
+using Ydb.Sdk.Ado.YdbType;
+
+namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
+
+public class YdbTimeSpanTypeMapping : YdbTypeMapping
+{
+ public YdbTimeSpanTypeMapping(YdbDbType ydbDbType) : base(typeof(TimeSpan), ydbDbType)
+ {
+ }
+
+ private YdbTimeSpanTypeMapping(RelationalTypeMappingParameters parameters, YdbDbType ydbDbType)
+ : base(parameters, ydbDbType)
+ {
+ }
+
+ protected override YdbTimeSpanTypeMapping Clone(RelationalTypeMappingParameters parameters) =>
+ new(parameters, YdbDbType);
+
+ protected override string GenerateNonNullSqlLiteral(object value) =>
+ $"{YdbDbType}('{XmlConvert.ToString((TimeSpan)value)}')";
+}
diff --git a/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTypeMapping.cs b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTypeMapping.cs
new file mode 100644
index 00000000..1c6de2c8
--- /dev/null
+++ b/src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbTypeMapping.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Data.Common;
+using Microsoft.EntityFrameworkCore.Storage;
+using Microsoft.EntityFrameworkCore.Storage.Json;
+using Ydb.Sdk.Ado;
+using Ydb.Sdk.Ado.YdbType;
+
+namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
+
+public abstract class YdbTypeMapping(
+ RelationalTypeMapping.RelationalTypeMappingParameters parameters,
+ YdbDbType ydbDbType
+) : RelationalTypeMapping(parameters), IYdbTypeMapping
+{
+ public YdbDbType YdbDbType { get; } = ydbDbType;
+
+ protected YdbTypeMapping(
+ Type clrType,
+ YdbDbType ydbDbType,
+ JsonValueReaderWriter? jsonValueReaderWriter = null
+ ) : this(
+ new RelationalTypeMappingParameters(
+ new CoreTypeMappingParameters(clrType, jsonValueReaderWriter: jsonValueReaderWriter),
+ ydbDbType.ToString()
+ ), ydbDbType
+ )
+ {
+ }
+
+ protected override void ConfigureParameter(DbParameter parameter)
+ {
+ if (parameter is not YdbParameter ydbParameter)
+ {
+ throw new InvalidOperationException(
+ $"Ydb-specific type mapping {GetType().Name} being used with non-Ydb parameter type {parameter.GetType().Name}");
+ }
+
+ base.ConfigureParameter(parameter);
+ ydbParameter.YdbDbType = YdbDbType;
+ }
+}
diff --git a/src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs b/src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs
index b01b160e..4d433711 100644
--- a/src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs
+++ b/src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs
@@ -38,19 +38,26 @@ RelationalTypeMappingSourceDependencies relationalDependencies
private static readonly YdbDecimalTypeMapping Decimal = YdbDecimalTypeMapping.Default;
- private static readonly GuidTypeMapping Guid = YdbGuidTypeMapping.Default;
+ private static readonly GuidTypeMapping Uuid = YdbGuidTypeMapping.Default;
private static readonly YdbTextTypeMapping Text = YdbTextTypeMapping.Default;
private static readonly YdbBytesTypeMapping Bytes = YdbBytesTypeMapping.Default;
private static readonly YdbJsonTypeMapping Json = new("Json", typeof(JsonElement), null);
- private static readonly YdbDateOnlyTypeMapping Date = new("Date");
- private static readonly DateTimeTypeMapping DateTime = new("DateTime");
+ private static readonly YdbDateOnlyTypeMapping DateDateOnly = new(YdbDbType.Date);
+ private static readonly YdbDateOnlyTypeMapping Date32DateOnly = new(YdbDbType.Date32);
- private static readonly YdbDateTimeTypeMapping Timestamp = new("Timestamp", DbType.DateTime);
+ private static readonly YdbDateTimeTypeMapping DateDateTime = new(YdbDbType.Date);
+ private static readonly YdbDateTimeTypeMapping Date32DateTime = new(YdbDbType.Date32);
- // TODO: Await interval in Ydb.Sdk
- private static readonly TimeSpanTypeMapping Interval = new("Interval", DbType.Object);
+ private static readonly YdbDateTimeTypeMapping Datetime = new(YdbDbType.Datetime);
+ private static readonly YdbDateTimeTypeMapping Datetime64 = new(YdbDbType.Datetime64);
+
+ private static readonly YdbDateTimeTypeMapping Timestamp = new(YdbDbType.Timestamp);
+ private static readonly YdbDateTimeTypeMapping Timestamp64 = new(YdbDbType.Timestamp64);
+
+ private static readonly YdbTimeSpanTypeMapping Interval = new(YdbDbType.Interval);
+ private static readonly YdbTimeSpanTypeMapping Interval64 = new(YdbDbType.Interval64);
#endregion
@@ -72,17 +79,20 @@ RelationalTypeMappingSourceDependencies relationalDependencies
{ "Float", [Float] },
{ "Double", [Double] },
- { "Guid", [Guid] },
-
- { "Date", [Date] },
- { "DateTime", [DateTime] },
- { "Timestamp", [Timestamp] },
- { "Interval", [Interval] },
+ { "Guid", [Uuid] },
{ "Text", [Text] },
{ "Bytes", [Bytes] },
- { "Json", [Json] }
+ { "Date", [DateDateTime, DateDateOnly] },
+ { "DateTime", [Datetime] },
+ { "Timestamp", [Timestamp] },
+ { "Interval", [Interval] },
+
+ { "Date32", [Date32DateTime, Date32DateOnly] },
+ { "Datetime64", [Datetime64] },
+ { "Timestamp64", [Timestamp64] },
+ { "Interval64", [Interval64] }
};
private static readonly Dictionary ClrTypeMapping = new()
@@ -102,13 +112,13 @@ RelationalTypeMappingSourceDependencies relationalDependencies
{ typeof(float), Float },
{ typeof(double), Double },
- { typeof(Guid), Guid },
+ { typeof(Guid), Uuid },
{ typeof(string), Text },
{ typeof(byte[]), Bytes },
{ typeof(JsonElement), Json },
- { typeof(DateOnly), Date },
+ { typeof(DateOnly), DateDateOnly },
{ typeof(DateTime), Timestamp },
{ typeof(TimeSpan), Interval }
};
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/InsertWithDefaultsTests.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/InsertWithDefaultsTests.cs
new file mode 100644
index 00000000..32a38ddd
--- /dev/null
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/InsertWithDefaultsTests.cs
@@ -0,0 +1,168 @@
+using EntityFrameworkCore.Ydb.Extensions;
+using EntityFrameworkCore.Ydb.FunctionalTests.TestUtilities;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Xunit;
+
+namespace EntityFrameworkCore.Ydb.FunctionalTests;
+
+public class InsertWithDefaultsTests
+{
+ [Fact]
+ public async Task Insert_WritesDefaultedNonNullColumns_Succeeds()
+ {
+ await using var testStore = YdbTestStoreFactory.Instance.Create("InsertWithDefaultsTests");
+
+ await using var dbContext = new TestEntityDbContext();
+ await testStore.CleanAsync(dbContext);
+
+ await dbContext.Database.MigrateAsync();
+
+ dbContext.Entities.AddRange(new TestEntity { Id = 1 }, new TestEntity { Id = 2 }, new TestEntity { Id = 3 });
+ await dbContext.SaveChangesAsync();
+
+ foreach (var entity in dbContext.Entities.ToList())
+ {
+ Assert.True(entity.BoolValue);
+ Assert.Equal(1, entity.Int8Value);
+ Assert.Equal(1, entity.Int16Value);
+ Assert.Equal(1, entity.Int32Value);
+ Assert.Equal(1, entity.Int64Value);
+
+ Assert.Equal(1, entity.Uint8Value);
+ Assert.Equal(1, entity.Uint16Value);
+ Assert.Equal(1u, entity.Uint32Value);
+ Assert.Equal(1u, entity.Uint64Value);
+
+ Assert.Equal(1, entity.FloatValue);
+ Assert.Equal(1, entity.DoubleValue);
+ Assert.Equal(1.00000m, entity.DecimalValue);
+
+ Assert.Equal(Guid.Empty, entity.GuidValue);
+
+ Assert.Equal("text", entity.TextValue);
+ Assert.Empty(entity.BytesValue);
+
+ Assert.Equal(new DateOnly(1971, 12, 1), entity.DateValue);
+ Assert.Equal(new DateTime(1971, 12, 1, 0, 0, 0, DateTimeKind.Utc), entity.DateTimeValue);
+ Assert.Equal(TimeSpan.FromSeconds(1), entity.IntervalValue);
+ }
+ }
+
+ public class TestEntityDbContext : DbContext
+ {
+ public DbSet Entities => Set();
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder) =>
+ modelBuilder.Entity(e =>
+ {
+ e.ToTable("TestEntity");
+ e.HasKey(x => x.Id);
+ e.Property(x => x.BoolValue).HasDefaultValue(true);
+ e.Property(x => x.Int8Value).HasDefaultValue(1);
+ e.Property(x => x.Int16Value).HasDefaultValue(1);
+ e.Property(x => x.Int32Value).HasDefaultValue(1);
+ e.Property(x => x.Int64Value).HasDefaultValue(1);
+ e.Property(x => x.Uint8Value).HasDefaultValue(1);
+ e.Property(x => x.Uint16Value).HasDefaultValue(1);
+ e.Property(x => x.Uint32Value).HasDefaultValue(1);
+ e.Property(x => x.Uint64Value).HasDefaultValue(1);
+ e.Property(x => x.FloatValue).HasDefaultValue(1);
+ e.Property(x => x.DoubleValue).HasDefaultValue(1);
+ e.Property(x => x.DecimalValue).HasDefaultValue(1);
+ e.Property(x => x.GuidValue).HasDefaultValue(Guid.Empty);
+ e.Property(x => x.TextValue).HasDefaultValue("text");
+ e.Property(x => x.BytesValue).HasDefaultValue(Array.Empty());
+ e.Property(x => x.DateValue).HasDefaultValue(new DateOnly(1971, 12, 1));
+ e.Property(x => x.DateTimeValue).HasDefaultValue(new DateTime(1971, 12, 1, 0, 0, 0, DateTimeKind.Utc));
+ e.Property(x => x.IntervalValue).HasDefaultValue(TimeSpan.FromSeconds(1));
+ });
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder
+ .UseYdb("Host=localhost;Port=2136", builder => builder
+ .DisableRetryOnFailure()
+ .MigrationsAssembly(typeof(TestEntityMigration).Assembly.FullName))
+ .EnableServiceProviderCaching(false)
+ .LogTo(Console.WriteLine);
+ }
+
+ public class TestEntity
+ {
+ public int Id { get; set; }
+
+ public bool? BoolValue { get; set; }
+
+ public sbyte Int8Value { get; set; }
+ public short Int16Value { get; set; }
+ public int Int32Value { get; set; }
+ public long Int64Value { get; set; }
+
+ public byte Uint8Value { get; set; }
+ public ushort Uint16Value { get; set; }
+ public uint Uint32Value { get; set; }
+ public ulong Uint64Value { get; set; }
+
+ public float FloatValue { get; set; }
+ public double DoubleValue { get; set; }
+ [Precision(25, 5)] public decimal DecimalValue { get; set; }
+
+ public Guid GuidValue { get; set; }
+
+ public string TextValue { get; set; } = null!;
+ public byte[] BytesValue { get; set; } = null!;
+
+ public DateOnly DateValue { get; set; }
+ public DateTime DateTimeValue { get; set; }
+ public TimeSpan IntervalValue { get; set; }
+ }
+
+ [DbContext(typeof(TestEntityDbContext))]
+ [Migration("InsertWithDefaultsTests_TestEntity")]
+ private class TestEntityMigration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder) =>
+ migrationBuilder.CreateTable(
+ name: "TestEntity",
+ columns: table => new
+ {
+ Id = table.Column(type: "Int32", nullable: false),
+
+ BoolValue = table.Column(type: "Bool", nullable: false, defaultValue: true),
+
+ Int8Value = table.Column(type: "Int8", nullable: false, defaultValue: (sbyte)1),
+ Int16Value = table.Column(type: "Int16", nullable: false, defaultValue: (short)1),
+ Int32Value = table.Column(type: "Int32", nullable: false, defaultValue: 1),
+ Int64Value = table.Column(type: "Int64", nullable: false, defaultValue: 1L),
+
+ Uint8Value = table.Column(type: "Uint8", nullable: false, defaultValue: (byte)1),
+ Uint16Value = table.Column(type: "Uint16", nullable: false, defaultValue: (ushort)1),
+ Uint32Value = table.Column(type: "Uint32", nullable: false, defaultValue: (uint)1),
+ Uint64Value = table.Column(type: "Uint64", nullable: false, defaultValue: (ulong)1),
+
+ FloatValue = table.Column(type: "Float", nullable: false, defaultValue: 1f),
+ DoubleValue = table.Column(type: "Double", nullable: false, defaultValue: 1d),
+
+ DecimalValue = table.Column(type: "Decimal(25, 5)", precision: 25, scale: 5,
+ nullable: false, defaultValue: 1.0m),
+
+ GuidValue = table.Column(type: "Uuid", nullable: false, defaultValue: Guid.Empty),
+
+ TextValue = table.Column(type: "Text", nullable: false, defaultValue: "text"),
+ BytesValue =
+ table.Column(type: "Bytes", nullable: false, defaultValue: Array.Empty()),
+
+ DateValue = table.Column(type: "Date", nullable: false,
+ defaultValue: new DateOnly(1971, 12, 1)),
+ DateTimeValue = table.Column(type: "Timestamp", nullable: false,
+ defaultValue: new DateTime(1971, 12, 1, 0, 0, 0, DateTimeKind.Utc)),
+ IntervalValue = table.Column(type: "Interval", nullable: false,
+ defaultValue: TimeSpan.FromSeconds(1))
+ },
+ constraints: table => { table.PrimaryKey("PK_TestEntity", x => x.Id); }
+ );
+
+ protected override void Down(MigrationBuilder migrationBuilder) =>
+ migrationBuilder.DropTable(name: "TestEntity");
+ }
+}
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/LazyLoadProxyYdbTest.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/LazyLoadProxyYdbTest.cs
index 221c6d57..07b3057d 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/LazyLoadProxyYdbTest.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/LazyLoadProxyYdbTest.cs
@@ -7,12 +7,9 @@
namespace EntityFrameworkCore.Ydb.FunctionalTests;
-public class LazyLoadProxyYdbTest : LazyLoadProxyTestBase
+public class LazyLoadProxyYdbTest(LazyLoadProxyYdbTest.LoadYdbFixture fixture)
+ : LazyLoadProxyTestBase(fixture)
{
- public LazyLoadProxyYdbTest(LoadYdbFixture fixture) : base(fixture)
- {
- }
-
[ConditionalFact(Skip = "TODO: Fix precision")]
public override void Can_serialize_proxies_to_JSON() => base.Can_serialize_proxies_to_JSON();
@@ -32,5 +29,18 @@ public TestSqlLoggerFactory TestSqlLoggerFactory
protected override ITestStoreFactory TestStoreFactory
=> YdbTestStoreFactory.Instance;
+
+ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
+ {
+ base.OnModelCreating(modelBuilder, context);
+
+ modelBuilder.Entity()
+ .Property(e => e.Birthday)
+ .HasColumnType("Timestamp64");
+
+ modelBuilder.Entity()
+ .Property(e => e.Birthday)
+ .HasColumnType("Timestamp64");
+ }
}
}
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/GearsOfWarQueryYdbFixture.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/GearsOfWarQueryYdbFixture.cs
index 0dddba35..7643fe1a 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/GearsOfWarQueryYdbFixture.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/GearsOfWarQueryYdbFixture.cs
@@ -20,14 +20,18 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
modelBuilder.Entity()
.Property(e => e.Date)
- .HasConversion(
- v => v.ToDateTime(TimeOnly.MinValue),
- v => DateOnly.FromDateTime(v));
+ .HasColumnType("Date32");
modelBuilder.Entity()
- .Property(e => e.Duration)
+ .Property(e => e.Time)
+ .HasColumnType("Datetime64")
.HasConversion(
- v => new DateTime(1970, 1, 1).Add(v),
- v => v.TimeOfDay);
+ v => new DateTime(2000, 1, 1).Add(v.ToTimeSpan()), // Time → DateTime
+ v => new TimeOnly(v.TimeOfDay.Ticks)
+ ); // DateTime → Time
+
+ modelBuilder.Entity()
+ .Property(e => e.IssueDate)
+ .HasColumnType("Date32");
}
}
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPCGearsOfWarQueryYdbFixture.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPCGearsOfWarQueryYdbFixture.cs
index c443d65c..076710e0 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPCGearsOfWarQueryYdbFixture.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPCGearsOfWarQueryYdbFixture.cs
@@ -15,18 +15,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
+ modelBuilder.Entity()
+ .Property(e => e.Date)
+ .HasColumnType("Date32");
+
modelBuilder.Entity()
.Property(e => e.Time)
+ .HasColumnType("Datetime64")
.HasConversion(
v => new DateTime(2000, 1, 1).Add(v.ToTimeSpan()), // Time → DateTime
- v => new TimeOnly(v.TimeOfDay.Ticks)) // DateTime → Time
- .HasColumnType("DATETIME");
+ v => new TimeOnly(v.TimeOfDay.Ticks)
+ ); // DateTime → Time
- modelBuilder.Entity()
- .Property(e => e.Duration)
- .HasConversion(
- v => new DateTime(2000, 1, 1).Add(v), // TimeSpan → DateTime
- v => v.TimeOfDay) // DateTime → TimeSpan
- .HasColumnType("DATETIME");
+ modelBuilder.Entity()
+ .Property(e => e.IssueDate)
+ .HasColumnType("Date32");
}
}
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbFixture.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbFixture.cs
index ce97a173..27d6c319 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbFixture.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbFixture.cs
@@ -6,7 +6,7 @@
namespace EntityFrameworkCore.Ydb.FunctionalTests.Query;
-public class TptGearsOfWarQueryYdbFixture : TPTGearsOfWarQueryRelationalFixture
+public class TPTGearsOfWarQueryYdbFixture : TPTGearsOfWarQueryRelationalFixture
{
protected override ITestStoreFactory TestStoreFactory
=> YdbTestStoreFactory.Instance;
@@ -15,18 +15,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
base.OnModelCreating(modelBuilder, context);
+ modelBuilder.Entity()
+ .Property(e => e.Date)
+ .HasColumnType("Date32");
+
modelBuilder.Entity()
.Property(e => e.Time)
+ .HasColumnType("Datetime64")
.HasConversion(
v => new DateTime(2000, 1, 1).Add(v.ToTimeSpan()), // Time → DateTime
- v => new TimeOnly(v.TimeOfDay.Ticks)) // DateTime → Time
- .HasColumnType("DATETIME");
+ v => new TimeOnly(v.TimeOfDay.Ticks)
+ ); // DateTime → Time
- modelBuilder.Entity()
- .Property(e => e.Date)
- .HasConversion(
- v => new DateTime(v.Year, v.Month, v.Day), // DateOnly → DateTime
- v => new DateOnly(v.Year, v.Month, v.Day)) // DateTime → DateOnly
- .HasColumnType("DATETIME");
+ modelBuilder.Entity()
+ .Property(e => e.IssueDate)
+ .HasColumnType("Date32");
}
}
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbTest.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbTest.cs
index 74b71e79..b66e3533 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbTest.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/TPTGearsOfWarQueryYdbTest.cs
@@ -5,10 +5,10 @@
namespace EntityFrameworkCore.Ydb.FunctionalTests.Query;
// TODO: Correlated subqueries right now are not supported in YDB
-public class TptGearsOfWarQueryYdbTest : TPTGearsOfWarQueryRelationalTestBase
+public class TPTGearsOfWarQueryYdbTest : TPTGearsOfWarQueryRelationalTestBase
{
// ReSharper disable once UnusedParameter.Local
- public TptGearsOfWarQueryYdbTest(TptGearsOfWarQueryYdbFixture fixture, ITestOutputHelper testOutputHelper)
+ public TPTGearsOfWarQueryYdbTest(TPTGearsOfWarQueryYdbFixture fixture, ITestOutputHelper testOutputHelper)
: base(fixture)
{
Fixture.TestSqlLoggerFactory.Clear();
diff --git a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/SqlQueryCollectionParameterTests.cs b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/SqlQueryCollectionParameterTests.cs
index 0fe025e9..db088143 100644
--- a/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/SqlQueryCollectionParameterTests.cs
+++ b/src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/SqlQueryCollectionParameterTests.cs
@@ -38,6 +38,8 @@ public static IEnumerable