Improve counter type handling. (#112)

pull/114/head
Laurents Meyer 4 years ago committed by GitHub
parent c09660785d
commit a9c7a11f0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -696,6 +696,35 @@ namespace Microsoft.EntityFrameworkCore.Migrations
}
}
}
protected override void ColumnDefinition(
string schema,
string table,
string name,
ColumnOperation operation,
IModel model,
MigrationCommandListBuilder builder)
{
Check.NotEmpty(name, nameof(name));
Check.NotNull(operation, nameof(operation));
Check.NotNull(builder, nameof(builder));
if (operation.ComputedColumnSql != null)
{
ComputedColumnDefinition(schema, table, name, operation, model, builder);
return;
}
var columnType = GetColumnType(schema, table, name, operation, model);
builder
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(name))
.Append(" ")
.Append(columnType);
builder.Append(operation.IsNullable ? " NULL" : " NOT NULL");
DefaultValue(operation.DefaultValue, operation.DefaultValueSql, columnType, builder);
}
protected override string GetColumnType(
[CanBeNull] string schema,
@ -704,27 +733,34 @@ namespace Microsoft.EntityFrameworkCore.Migrations
[NotNull] ColumnOperation operation,
[CanBeNull] IModel model)
{
var storeType = base.GetColumnType(schema, table, name, operation, model);
var identity = operation[JetAnnotationNames.Identity] as string;
if (identity != null
|| operation[JetAnnotationNames.ValueGenerationStrategy] as JetValueGenerationStrategy?
== JetValueGenerationStrategy.IdentityColumn)
var storeType = operation.ColumnType;
if (IsIdentity(operation))
{
if (string.Equals(storeType, "counter", StringComparison.OrdinalIgnoreCase) ||
string.Equals(storeType, "identity", StringComparison.OrdinalIgnoreCase) ||
string.Equals(storeType, "autoincrement", StringComparison.OrdinalIgnoreCase) ||
string.Equals(storeType, "integer", StringComparison.OrdinalIgnoreCase))
// This column represents the actual identity.
if (storeType != null &&
Dependencies.TypeMappingSource.FindMapping(storeType) is JetIntTypeMapping)
{
storeType = "counter";
if (!string.IsNullOrEmpty(identity)
&& identity != "1, 1")
{
storeType += $"({identity})";
}
}
}
else if (storeType != null &&
IsExplicitIdentityColumnType(storeType))
{
// While this column uses an identity type (e.g. counter), it is not an actual identity column, because
// it was not marked as one.
storeType = "integer";
}
storeType ??= base.GetColumnType(schema, table, name, operation, model);
if (string.Equals(storeType, "counter", StringComparison.OrdinalIgnoreCase) &&
operation[JetAnnotationNames.Identity] is string identity &&
!string.IsNullOrEmpty(identity) &&
identity != "1, 1")
{
storeType += $"({identity})";
}
return storeType;
}
@ -969,6 +1005,11 @@ namespace Microsoft.EntityFrameworkCore.Migrations
|| operation[JetAnnotationNames.ValueGenerationStrategy] as JetValueGenerationStrategy?
== JetValueGenerationStrategy.IdentityColumn;
private static bool IsExplicitIdentityColumnType(string columnType)
=> string.Equals("counter", columnType, StringComparison.OrdinalIgnoreCase) ||
string.Equals("identity", columnType, StringComparison.OrdinalIgnoreCase) ||
string.Equals("autoincrement", columnType, StringComparison.OrdinalIgnoreCase);
#region Schemas not supported
protected override void Generate(EnsureSchemaOperation operation, IModel model, MigrationCommandListBuilder builder)

@ -0,0 +1,24 @@
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Storage;
namespace EntityFrameworkCore.Jet.Storage.Internal
{
public class JetIntTypeMapping : IntTypeMapping
{
public JetIntTypeMapping([NotNull] string storeType)
: base(storeType, System.Data.DbType.Int32)
{
}
protected JetIntTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
// JetIntTypeMapping is also used for an explicit counter type, because we actually want it to be integer unless
// the value generation type is also OnAdd.
// We therefore lock the store type to its original value (which should be "integer").
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
=> new JetIntTypeMapping(parameters.WithStoreTypeAndSize(Parameters.StoreType, parameters.Size));
}
}

@ -25,11 +25,13 @@ namespace EntityFrameworkCore.Jet.Storage.Internal
private readonly JetBoolTypeMapping _bit = new JetBoolTypeMapping("bit"); // JET bits are not nullable
private readonly JetBoolTypeMapping _bool = new JetBoolTypeMapping("smallint");
private readonly IntTypeMapping _counter = new IntTypeMapping("counter", DbType.Int32);
// We just map counter etc. to integer. Whether an integer property/column is actually a counter
// is determined by the value generation type.
private readonly IntTypeMapping _counter = new JetIntTypeMapping("integer");
private readonly ByteTypeMapping _byte = new ByteTypeMapping("byte", DbType.Byte); // unsigned, there is no signed byte in Jet
private readonly ShortTypeMapping _smallint = new ShortTypeMapping("smallint", DbType.Int16);
private readonly IntTypeMapping _integer = new IntTypeMapping("integer", DbType.Int32);
private readonly IntTypeMapping _integer = new JetIntTypeMapping("integer");
// private readonly JetDecimalTypeMapping _bigint = new JetDecimalTypeMapping("decimal", DbType.Decimal, precision: 28, scale: 0, StoreTypePostfix.PrecisionAndScale);
private readonly JetFloatTypeMapping _single = new JetFloatTypeMapping("single");
@ -97,7 +99,7 @@ namespace EntityFrameworkCore.Jet.Storage.Internal
{"logical", new[] {_bit}},
{"logical1", new[] {_bit}},
{"yesno", new[] {_bit}},
{"counter", new[] {_counter}},
{"identity", new[] {_counter}},
{"autoincrement", new[] {_counter}},
@ -114,7 +116,7 @@ namespace EntityFrameworkCore.Jet.Storage.Internal
{"long", new[] {_integer}},
{"int", new[] {_integer}},
{"integer4", new[] {_integer}},
{"single", new[] {_single}},
{"real", new[] {_single}},
{"float4", new[] {_single}},

@ -33,8 +33,321 @@ namespace EntityFrameworkCore.Jet
Assert.Single(cookies);
Assert.Equal(new DateTime(2021, 12, 31), cookies[0].BestServedBefore);
AssertSql(
$@"CREATE TABLE `Cookie` (
`CookieId` integer NOT NULL,
`Name` longchar NULL,
`BestServedBefore` datetime NOT NULL DEFAULT #2021-12-31#,
CONSTRAINT `PK_Cookie` PRIMARY KEY (`CookieId`)
);
INSERT INTO `Cookie` (`CookieId`, `Name`)
VALUES (1, 'Basic');
SELECT `c`.`CookieId`, `c`.`BestServedBefore`, `c`.`Name`
FROM `Cookie` AS `c`");
}
[ConditionalFact]
public virtual void Create_many_to_many_table_with_explicit_counter_column_type()
{
using var context = CreateContext(
model: builder =>
{
builder.Entity<Cookie>(entity =>
{
entity.Property(e => e.CookieId)
.HasColumnType("counter");
entity.HasData(
new Cookie { CookieId = 1, Name = "Chocolate Chip" });
});
builder.Entity<Backery>(entity =>
{
entity.Property(e => e.BackeryId)
.HasColumnType("counter");
entity.HasData(
new Backery { BackeryId = 1, Name = "Bread & Cookies" });
});
builder.Entity<CookieBackery>(entity =>
{
entity.HasKey(e => new { e.CookieId, e.BackeryId });
entity.HasOne(d => d.Cookie)
.WithMany()
.HasForeignKey(d => d.CookieId);
entity.HasOne(d => d.Backery)
.WithMany()
.HasForeignKey(d => d.BackeryId);
entity.HasData(
new CookieBackery { CookieId = 1, BackeryId = 1 });
});
});
var cookieBackeries = context.Set<CookieBackery>()
.Include(cb => cb.Cookie)
.Include(cb => cb.Backery)
.ToList();
Assert.Single(cookieBackeries);
Assert.Equal(1, cookieBackeries[0].Cookie.CookieId);
Assert.Equal(1, cookieBackeries[0].Backery.BackeryId);
AssertSql(
$@"CREATE TABLE `Backery` (
`BackeryId` counter NOT NULL,
`Name` longchar NULL,
CONSTRAINT `PK_Backery` PRIMARY KEY (`BackeryId`)
);
CREATE TABLE `Cookie` (
`CookieId` counter NOT NULL,
`Name` longchar NULL,
`BestServedBefore` datetime NOT NULL,
CONSTRAINT `PK_Cookie` PRIMARY KEY (`CookieId`)
);
CREATE TABLE `CookieBackery` (
`CookieId` integer NOT NULL,
`BackeryId` integer NOT NULL,
CONSTRAINT `PK_CookieBackery` PRIMARY KEY (`CookieId`, `BackeryId`),
CONSTRAINT `FK_CookieBackery_Backery_BackeryId` FOREIGN KEY (`BackeryId`) REFERENCES `Backery` (`BackeryId`) ON DELETE CASCADE,
CONSTRAINT `FK_CookieBackery_Cookie_CookieId` FOREIGN KEY (`CookieId`) REFERENCES `Cookie` (`CookieId`) ON DELETE CASCADE
);
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
INSERT INTO `CookieBackery` (`CookieId`, `BackeryId`)
VALUES (1, 1);
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);
SELECT `c`.`CookieId`, `c`.`BackeryId`, `c0`.`CookieId`, `c0`.`BestServedBefore`, `c0`.`Name`, `b`.`BackeryId`, `b`.`Name`
FROM (`CookieBackery` AS `c`
INNER JOIN `Cookie` AS `c0` ON `c`.`CookieId` = `c0`.`CookieId`)
INNER JOIN `Backery` AS `b` ON `c`.`BackeryId` = `b`.`BackeryId`");
}
[ConditionalFact]
public virtual void Create_many_to_many_table_with_explicit_int_column_type()
{
using var context = CreateContext(
model: builder =>
{
builder.Entity<Cookie>(entity =>
{
entity.Property(e => e.CookieId)
.HasColumnType("int");
entity.HasData(
new Cookie { CookieId = 1, Name = "Chocolate Chip" });
});
builder.Entity<Backery>(entity =>
{
entity.Property(e => e.BackeryId)
.HasColumnType("int");
entity.HasData(
new Backery { BackeryId = 1, Name = "Bread & Cookies" });
});
builder.Entity<CookieBackery>(entity =>
{
entity.HasKey(e => new { e.CookieId, e.BackeryId });
entity.HasOne(d => d.Cookie)
.WithMany()
.HasForeignKey(d => d.CookieId);
entity.HasOne(d => d.Backery)
.WithMany()
.HasForeignKey(d => d.BackeryId);
entity.HasData(
new CookieBackery { CookieId = 1, BackeryId = 1 });
});
});
var cookieBackeries = context.Set<CookieBackery>()
.Include(cb => cb.Cookie)
.Include(cb => cb.Backery)
.ToList();
Assert.Single(cookieBackeries);
Assert.Equal(1, cookieBackeries[0].Cookie.CookieId);
Assert.Equal(1, cookieBackeries[0].Backery.BackeryId);
AssertSql(
$@"CREATE TABLE `Backery` (
`BackeryId` counter NOT NULL,
`Name` longchar NULL,
CONSTRAINT `PK_Backery` PRIMARY KEY (`BackeryId`)
);
CREATE TABLE `Cookie` (
`CookieId` counter NOT NULL,
`Name` longchar NULL,
`BestServedBefore` datetime NOT NULL,
CONSTRAINT `PK_Cookie` PRIMARY KEY (`CookieId`)
);
CREATE TABLE `CookieBackery` (
`CookieId` integer NOT NULL,
`BackeryId` integer NOT NULL,
CONSTRAINT `PK_CookieBackery` PRIMARY KEY (`CookieId`, `BackeryId`),
CONSTRAINT `FK_CookieBackery_Backery_BackeryId` FOREIGN KEY (`BackeryId`) REFERENCES `Backery` (`BackeryId`) ON DELETE CASCADE,
CONSTRAINT `FK_CookieBackery_Cookie_CookieId` FOREIGN KEY (`CookieId`) REFERENCES `Cookie` (`CookieId`) ON DELETE CASCADE
);
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
INSERT INTO `CookieBackery` (`CookieId`, `BackeryId`)
VALUES (1, 1);
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);
SELECT `c`.`CookieId`, `c`.`BackeryId`, `c0`.`CookieId`, `c0`.`BestServedBefore`, `c0`.`Name`, `b`.`BackeryId`, `b`.`Name`
FROM (`CookieBackery` AS `c`
INNER JOIN `Cookie` AS `c0` ON `c`.`CookieId` = `c0`.`CookieId`)
INNER JOIN `Backery` AS `b` ON `c`.`BackeryId` = `b`.`BackeryId`");
}
[ConditionalFact]
public virtual void Create_many_to_many_table_with_inappropriate_counter_column_type()
{
using var context = CreateContext(
model: builder =>
{
builder.Entity<Cookie>(entity =>
{
entity.Property(e => e.CookieId)
.HasColumnType("int");
entity.HasData(
new Cookie { CookieId = 1, Name = "Chocolate Chip" });
});
builder.Entity<Backery>(entity =>
{
entity.Property(e => e.BackeryId)
.HasColumnType("int");
entity.HasData(
new Backery { BackeryId = 1, Name = "Bread & Cookies" });
});
builder.Entity<CookieBackery>(entity =>
{
entity.HasKey(e => new { e.CookieId, e.BackeryId });
entity.Property(e => e.CookieId)
.HasColumnType("counter");
entity.Property(e => e.BackeryId)
.HasColumnType("counter");
entity.HasOne(d => d.Cookie)
.WithMany()
.HasForeignKey(d => d.CookieId);
entity.HasOne(d => d.Backery)
.WithMany()
.HasForeignKey(d => d.BackeryId);
entity.HasData(
new CookieBackery { CookieId = 1, BackeryId = 1 });
});
});
var cookieBackeries = context.Set<CookieBackery>()
.Include(cb => cb.Cookie)
.Include(cb => cb.Backery)
.ToList();
Assert.Single(cookieBackeries);
Assert.Equal(1, cookieBackeries[0].Cookie.CookieId);
Assert.Equal(1, cookieBackeries[0].Backery.BackeryId);
AssertSql(
$@"CREATE TABLE `Backery` (
`BackeryId` counter NOT NULL,
`Name` longchar NULL,
CONSTRAINT `PK_Backery` PRIMARY KEY (`BackeryId`)
);
CREATE TABLE `Cookie` (
`CookieId` counter NOT NULL,
`Name` longchar NULL,
`BestServedBefore` datetime NOT NULL,
CONSTRAINT `PK_Cookie` PRIMARY KEY (`CookieId`)
);
CREATE TABLE `CookieBackery` (
`CookieId` integer NOT NULL,
`BackeryId` integer NOT NULL,
CONSTRAINT `PK_CookieBackery` PRIMARY KEY (`CookieId`, `BackeryId`),
CONSTRAINT `FK_CookieBackery_Backery_BackeryId` FOREIGN KEY (`BackeryId`) REFERENCES `Backery` (`BackeryId`) ON DELETE CASCADE,
CONSTRAINT `FK_CookieBackery_Cookie_CookieId` FOREIGN KEY (`CookieId`) REFERENCES `Cookie` (`CookieId`) ON DELETE CASCADE
);
INSERT INTO `Backery` (`BackeryId`, `Name`)
VALUES (1, 'Bread & Cookies');
INSERT INTO `Cookie` (`CookieId`, `BestServedBefore`, `Name`)
VALUES (1, #1899-12-30#, 'Chocolate Chip');
INSERT INTO `CookieBackery` (`CookieId`, `BackeryId`)
VALUES (1, 1);
CREATE INDEX `IX_CookieBackery_BackeryId` ON `CookieBackery` (`BackeryId`);
SELECT `c`.`CookieId`, `c`.`BackeryId`, `c0`.`CookieId`, `c0`.`BestServedBefore`, `c0`.`Name`, `b`.`BackeryId`, `b`.`Name`
FROM (`CookieBackery` AS `c`
INNER JOIN `Cookie` AS `c0` ON `c`.`CookieId` = `c0`.`CookieId`)
INNER JOIN `Backery` AS `b` ON `c`.`BackeryId` = `b`.`BackeryId`");
}
private void AssertSql(string expected)
=> Assert.Equal(expected.Replace("\r\n", "\n"), Sql.Replace("\r\n", "\n"));
public class Cookie
{
public int CookieId { get; set; }
@ -42,6 +355,21 @@ namespace EntityFrameworkCore.Jet
public DateTime BestServedBefore { get; set; }
}
public class Backery
{
public int BackeryId { get; set; }
public string Name { get; set; }
}
public class CookieBackery
{
public int CookieId { get; set; }
public int BackeryId { get; set; }
public virtual Cookie Cookie { get; set; }
public virtual Backery Backery { get; set; }
}
public class Context : ContextBase
{
}

Loading…
Cancel
Save