From 4d972238543fe7c0aa3c3a36020a1ca37e05824c Mon Sep 17 00:00:00 2001 From: Thierry Habart Date: Sun, 19 Sep 2021 17:40:37 +0200 Subject: [PATCH] Ticket #25 : Can pass NULL value --- global.json | 3 +- .../EFCore.Cassandra.Samples/FakeDbContext.cs | 2 +- ...20210919135929_NullableInteger.Designer.cs | 140 ++++++++++++++++++ .../20210919135929_NullableInteger.cs | 31 ++++ .../Migrations/FakeDbContextModelSnapshot.cs | 3 + .../Models/Applicant.cs | 1 + .../Storage/BaseCassandraTypeMapping.cs | 54 +++++++ .../Storage/CassandraBoolTypeMapping.cs | 18 +++ .../Storage/CassandraDecimalTypeMapping.cs | 23 +++ .../Storage/CassandraDoubleTypeMapping.cs | 18 +++ .../Storage/CassandraFloatTypeMapping.cs | 24 +++ .../Storage/CassandraIntTypeMapping.cs | 18 +++ .../Storage/CassandraLongTypeMapping.cs | 18 +++ .../Storage/CassandraShortTypeMapping.cs | 18 +++ .../Internal/CassandraTypeMappingSource.cs | 14 +- .../CassandraModificationCommandBatch.cs | 2 + 16 files changed, 378 insertions(+), 9 deletions(-) create mode 100644 samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.Designer.cs create mode 100644 samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.cs create mode 100644 src/EFCore.Cassandra/Storage/BaseCassandraTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraBoolTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraDecimalTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraDoubleTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraFloatTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraIntTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraLongTypeMapping.cs create mode 100644 src/EFCore.Cassandra/Storage/CassandraShortTypeMapping.cs diff --git a/global.json b/global.json index 8c50959..3ef2287 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "5.0.103" + "version": "5.0.103", + "rollForward": "latestFeature" } } \ No newline at end of file diff --git a/samples/EFCore.Cassandra.Samples/FakeDbContext.cs b/samples/EFCore.Cassandra.Samples/FakeDbContext.cs index b62f54b..b3a6df9 100644 --- a/samples/EFCore.Cassandra.Samples/FakeDbContext.cs +++ b/samples/EFCore.Cassandra.Samples/FakeDbContext.cs @@ -60,7 +60,7 @@ private void ConfigureDataxAstra(DbContextOptionsBuilder optionsBuilder) private void ConfigureLocalDB(DbContextOptionsBuilder optionsBuilder) { - optionsBuilder.UseCassandra("Contact Points=127.0.0.1;", SCHEMA_NAME, opt => + optionsBuilder.UseCassandra("Contact Points=127.0.0.1;Username=cassandra;Password=password", SCHEMA_NAME, opt => { opt.MigrationsHistoryTable(HistoryRepository.DefaultTableName); }, o => { diff --git a/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.Designer.cs b/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.Designer.cs new file mode 100644 index 0000000..cd7800b --- /dev/null +++ b/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.Designer.cs @@ -0,0 +1,140 @@ +// +using System; +using System.Collections.Generic; +using System.Net; +using System.Numerics; +using Cassandra; +using EFCore.Cassandra.Samples; +using EFCore.Cassandra.Samples.Models; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace EFCore.Cassandra.Samples.Migrations +{ + [DbContext(typeof(FakeDbContext))] + [Migration("20210919135929_NullableInteger")] + partial class NullableInteger + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Cassandra:KeyspaceConfiguration", "{\"ReplicationFactor\":2,\"ReplicationClass\":0}") + .HasAnnotation("ProductVersion", "5.0.3"); + + modelBuilder.Entity("EFCore.Cassandra.Samples.Models.Applicant", b => + { + b.Property("Id") + .HasColumnType("uuid"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ApplicantId") + .HasColumnType("uuid"); + + b.Property("BigInteger") + .HasColumnType("varint"); + + b.Property("Blob") + .HasColumnType("blob"); + + b.Property("Bool") + .HasColumnType("boolean"); + + b.Property("DateTimeOffset") + .HasColumnType("timestamp"); + + b.Property("Decimal") + .HasColumnType("decimal"); + + b.Property>("Dic") + .HasColumnType("map"); + + b.Property("Double") + .HasColumnType("double"); + + b.Property("Float") + .HasColumnType("float"); + + b.Property("Integer") + .HasColumnType("int"); + + b.Property("Ip") + .HasColumnType("inet"); + + b.Property("LastName") + .HasColumnType("text"); + + b.Property("LocalDate") + .HasColumnType("date"); + + b.Property("LocalTime") + .HasColumnType("time"); + + b.Property("Long") + .HasColumnType("bigint"); + + b.Property>("Lst") + .HasColumnType("list"); + + b.Property>("LstInt") + .HasColumnType("list"); + + b.Property("NullableInteger") + .HasColumnType("int"); + + b.Property("Phones") + .HasColumnType("list>"); + + b.Property("Sbyte") + .HasColumnType("tinyint"); + + b.Property("SmallInt") + .HasColumnType("smallint"); + + b.Property("TimeUuid") + .HasColumnType("uuid"); + + b.HasKey("Id", "Order"); + + b.ToTable("applicants", "cv"); + + b + .HasAnnotation("Cassandra:ClusterColumns", new[] { "Order" }) + .HasAnnotation("Cassandra:ClusteringOrderByOptions", "[{\"ColumnName\":\"Order\",\"Order\":0}]"); + }); + + modelBuilder.Entity("EFCore.Cassandra.Samples.Models.ApplicantAddress", b => + { + b.Property("City") + .HasColumnType("text"); + + b.Property("StreetNumber") + .HasColumnType("int"); + + b.ToTable("applicant_addr", "cv"); + + b + .HasAnnotation("Cassandra:IsUserDefinedType", true); + }); + + modelBuilder.Entity("EFCore.Cassandra.Samples.Models.ApplicantPhone", b => + { + b.Property("IsMobile") + .HasColumnType("boolean"); + + b.Property("PhoneNumber") + .HasColumnType("text"); + + b.ToTable("applicant_phone", "cv"); + + b + .HasAnnotation("Cassandra:IsUserDefinedType", true); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.cs b/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.cs new file mode 100644 index 0000000..d02f6c3 --- /dev/null +++ b/samples/EFCore.Cassandra.Samples/Migrations/20210919135929_NullableInteger.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace EFCore.Cassandra.Samples.Migrations +{ + public partial class NullableInteger : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "cv"); + + migrationBuilder.AddColumn( + name: "NullableInteger", + schema: "cv", + table: "applicants", + type: "int", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "NullableInteger", + schema: "cv", + table: "applicants"); + + migrationBuilder.EnsureSchema( + name: "cv"); + } + } +} diff --git a/samples/EFCore.Cassandra.Samples/Migrations/FakeDbContextModelSnapshot.cs b/samples/EFCore.Cassandra.Samples/Migrations/FakeDbContextModelSnapshot.cs index 62de3e4..9e3c616 100644 --- a/samples/EFCore.Cassandra.Samples/Migrations/FakeDbContextModelSnapshot.cs +++ b/samples/EFCore.Cassandra.Samples/Migrations/FakeDbContextModelSnapshot.cs @@ -81,6 +81,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property>("LstInt") .HasColumnType("list"); + b.Property("NullableInteger") + .HasColumnType("int"); + b.Property("Phones") .HasColumnType("list>"); diff --git a/samples/EFCore.Cassandra.Samples/Models/Applicant.cs b/samples/EFCore.Cassandra.Samples/Models/Applicant.cs index c59bf8c..a43bd0b 100644 --- a/samples/EFCore.Cassandra.Samples/Models/Applicant.cs +++ b/samples/EFCore.Cassandra.Samples/Models/Applicant.cs @@ -20,6 +20,7 @@ public class Applicant public double Double { get; set; } public float Float { get; set; } public int Integer { get; set; } + public Nullable NullableInteger { get; set; } public short SmallInt { get; set; } public DateTimeOffset DateTimeOffset { get; set; } public TimeUuid TimeUuid { get; set; } diff --git a/src/EFCore.Cassandra/Storage/BaseCassandraTypeMapping.cs b/src/EFCore.Cassandra/Storage/BaseCassandraTypeMapping.cs new file mode 100644 index 0000000..2a8211e --- /dev/null +++ b/src/EFCore.Cassandra/Storage/BaseCassandraTypeMapping.cs @@ -0,0 +1,54 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using System; +using System.Data; +using System.Data.Common; + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public abstract class BaseCassandraTypeMapping : RelationalTypeMapping + { + public BaseCassandraTypeMapping( + string storeType, + Type clrType, + DbType? dbType) + : base(storeType, clrType, dbType) + { + } + + protected BaseCassandraTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + public override DbParameter CreateParameter(DbCommand command, string name, object value, bool? nullable = null) + { + var parameter = command.CreateParameter(); + parameter.Direction = ParameterDirection.Input; + parameter.ParameterName = name; + value = ConvertUnderlyingEnumValueToEnum(value); + if (Converter != null) + { + value = Converter.ConvertToProvider(value); + } + + parameter.Value = value ?? null; + + if (nullable.HasValue) + { + parameter.IsNullable = nullable.Value; + } + + if (DbType.HasValue) + { + parameter.DbType = DbType.Value; + } + + ConfigureParameter(parameter); + + return parameter; + } + + private object? ConvertUnderlyingEnumValueToEnum(object? value) + => value?.GetType().IsInteger() == true && ClrType.UnwrapNullableType().IsEnum + ? Enum.ToObject(ClrType.UnwrapNullableType(), value) + : value; + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraBoolTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraBoolTypeMapping.cs new file mode 100644 index 0000000..7acd25c --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraBoolTypeMapping.cs @@ -0,0 +1,18 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraBoolTypeMapping: BaseCassandraTypeMapping + { + public CassandraBoolTypeMapping(string storeType) + : base(storeType, typeof(bool), System.Data.DbType.Boolean) + { + } + + protected CassandraBoolTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraBoolTypeMapping(parameters); + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraDecimalTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraDecimalTypeMapping.cs new file mode 100644 index 0000000..6d2c858 --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraDecimalTypeMapping.cs @@ -0,0 +1,23 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraDecimalTypeMapping : BaseCassandraTypeMapping + { + private const string DecimalFormatConst = "{0:0.0###########################}"; + + public CassandraDecimalTypeMapping(string storeType) + : base(storeType, typeof(decimal), System.Data.DbType.Decimal) + { + } + + protected CassandraDecimalTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraDecimalTypeMapping(parameters); + + protected override string SqlLiteralFormatString + => DecimalFormatConst; + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraDoubleTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraDoubleTypeMapping.cs new file mode 100644 index 0000000..02dba6f --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraDoubleTypeMapping.cs @@ -0,0 +1,18 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraDoubleTypeMapping : BaseCassandraTypeMapping + { + public CassandraDoubleTypeMapping(string storeType) + : base(storeType, typeof(double), System.Data.DbType.Double) + { + } + + protected CassandraDoubleTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraDoubleTypeMapping(parameters); + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraFloatTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraFloatTypeMapping.cs new file mode 100644 index 0000000..5552aff --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraFloatTypeMapping.cs @@ -0,0 +1,24 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +using System; +using System.Globalization; + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraFloatTypeMapping : BaseCassandraTypeMapping + { + public CassandraFloatTypeMapping(string storeType) + : base(storeType, typeof(float), System.Data.DbType.Single) + { + } + + protected CassandraFloatTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraFloatTypeMapping(parameters); + + protected override string GenerateNonNullSqlLiteral(object value) + => Convert.ToSingle(value).ToString("R", CultureInfo.InvariantCulture); + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraIntTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraIntTypeMapping.cs new file mode 100644 index 0000000..51d5863 --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraIntTypeMapping.cs @@ -0,0 +1,18 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraIntTypeMapping: BaseCassandraTypeMapping + { + public CassandraIntTypeMapping(string storeType) + : base(storeType, typeof(int), System.Data.DbType.Int32) + { + } + + protected CassandraIntTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraIntTypeMapping(parameters); + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraLongTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraLongTypeMapping.cs new file mode 100644 index 0000000..276bead --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraLongTypeMapping.cs @@ -0,0 +1,18 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraLongTypeMapping: BaseCassandraTypeMapping + { + public CassandraLongTypeMapping(string storeType) + : base(storeType, typeof(long), System.Data.DbType.Int64) + { + } + + protected CassandraLongTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraLongTypeMapping(parameters); + } +} diff --git a/src/EFCore.Cassandra/Storage/CassandraShortTypeMapping.cs b/src/EFCore.Cassandra/Storage/CassandraShortTypeMapping.cs new file mode 100644 index 0000000..890b3df --- /dev/null +++ b/src/EFCore.Cassandra/Storage/CassandraShortTypeMapping.cs @@ -0,0 +1,18 @@ +// Copyright (c) SimpleIdServer. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Storage +{ + public class CassandraShortTypeMapping : BaseCassandraTypeMapping + { + public CassandraShortTypeMapping(string storeType) + : base(storeType, typeof(short), System.Data.DbType.Int16) + { + } + + protected CassandraShortTypeMapping(RelationalTypeMappingParameters parameters) : base(parameters) { } + + protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters) + => new CassandraShortTypeMapping(parameters); + } +} diff --git a/src/EFCore.Cassandra/Storage/Internal/CassandraTypeMappingSource.cs b/src/EFCore.Cassandra/Storage/Internal/CassandraTypeMappingSource.cs index 68cd5e9..8a97404 100644 --- a/src/EFCore.Cassandra/Storage/Internal/CassandraTypeMappingSource.cs +++ b/src/EFCore.Cassandra/Storage/Internal/CassandraTypeMappingSource.cs @@ -39,16 +39,16 @@ public class CassandraTypeMappingSource : RelationalTypeMappingSource private const string GuidTypeName = "uuid"; private const string DictionaryTypeName = "map"; private static readonly CassandraTypeMapping _text = new CassandraTypeMapping(TextTypeName, string.Empty, DbType.String); - private static readonly IntTypeMapping _integer = new IntTypeMapping(IntegerTypeName); - private static readonly LongTypeMapping _long = new LongTypeMapping(LongTypeName); + private static readonly CassandraIntTypeMapping _integer = new CassandraIntTypeMapping(IntegerTypeName); + private static readonly CassandraLongTypeMapping _long = new CassandraLongTypeMapping(LongTypeName); private static readonly CassandraTypeMapping _blob = new CassandraTypeMapping(BlobTypeName, new byte[0]); - private static readonly BoolTypeMapping _bool = new BoolTypeMapping(BoolTypeName); + private static readonly CassandraBoolTypeMapping _bool = new CassandraBoolTypeMapping(BoolTypeName); private static readonly CassandraTypeMapping _date = new CassandraTypeMapping(DateTypeName, new LocalDate(1, 1, 1)); - private static readonly DecimalTypeMapping _decimal = new DecimalTypeMapping(DecimalTypeName); - private static readonly DoubleTypeMapping _double = new DoubleTypeMapping(DoubleTypeName); - private static readonly FloatTypeMapping _float = new FloatTypeMapping(FloatTypeName); + private static readonly CassandraDecimalTypeMapping _decimal = new CassandraDecimalTypeMapping(DecimalTypeName); + private static readonly CassandraDoubleTypeMapping _double = new CassandraDoubleTypeMapping(DoubleTypeName); + private static readonly CassandraFloatTypeMapping _float = new CassandraFloatTypeMapping(FloatTypeName); private static readonly CassandraTypeMapping _ipAddr = new CassandraTypeMapping(IpTypeName, string.Empty, null, Cass.ColumnTypeCode.Inet); - private static readonly ShortTypeMapping _short = new ShortTypeMapping(SmallIntTypeName); + private static readonly CassandraShortTypeMapping _short = new CassandraShortTypeMapping(SmallIntTypeName); private static readonly CassandraTypeMapping _localTime = new CassandraTypeMapping(LocalTimeTypeName, new LocalTime(0, 0, 0, 0)); private static readonly CassandraTypeMapping _timeStamp = new CassandraTypeMapping(TimestampTypeName, default(DateTimeOffset)); private static readonly CassandraTypeMapping _timeUuid = new CassandraTypeMapping(TimeUuidTypeName, string.Empty, DbType.Guid); diff --git a/src/EFCore.Cassandra/Update/Internal/CassandraModificationCommandBatch.cs b/src/EFCore.Cassandra/Update/Internal/CassandraModificationCommandBatch.cs index 6a3f2a9..123b5c1 100644 --- a/src/EFCore.Cassandra/Update/Internal/CassandraModificationCommandBatch.cs +++ b/src/EFCore.Cassandra/Update/Internal/CassandraModificationCommandBatch.cs @@ -1,7 +1,9 @@ // Copyright (c) SimpleIdServer. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Update; +using System.Linq; namespace Microsoft.EntityFrameworkCore.Cassandra.Update.Internal {