You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1563 lines
56 KiB
C#
1563 lines
56 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using EntityFrameworkCore.Jet.FunctionalTests.TestUtilities;
|
|
using EntityFrameworkCore.Jet.Internal;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
|
using Microsoft.EntityFrameworkCore.Metadata;
|
|
using Microsoft.EntityFrameworkCore.Storage;
|
|
using Microsoft.EntityFrameworkCore.TestUtilities;
|
|
using Microsoft.EntityFrameworkCore.ValueGeneration.Internal;
|
|
using Xunit;
|
|
|
|
namespace EntityFrameworkCore.Jet.FunctionalTests;
|
|
|
|
#nullable disable
|
|
|
|
public abstract class JetValueGenerationScenariosTestBase
|
|
{
|
|
//protected static readonly GeometryFactory GeometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
|
|
|
|
protected abstract string DatabaseName { get; }
|
|
|
|
protected abstract Guid GuidSentinel { get; }
|
|
protected abstract int IntSentinel { get; }
|
|
protected abstract uint UIntSentinel { get; }
|
|
protected abstract IntKey IntKeySentinel { get; }
|
|
//protected abstract ULongKey ULongKeySentinel { get; }
|
|
protected abstract int? NullableIntSentinel { get; }
|
|
protected abstract string StringSentinel { get; }
|
|
protected abstract DateTime DateTimeSentinel { get; }
|
|
protected abstract NeedsConverter NeedsConverterSentinel { get; }
|
|
//protected abstract GeometryCollection GeometryCollectionSentinel { get; }
|
|
protected abstract byte[] TimestampSentinel { get; }
|
|
|
|
// Positive cases
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_Identity_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextIdentity(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(CreateBlog("One Unicorn"), CreateBlog("Two Unicorns"));
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextIdentity(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(1, blogs[0].Id);
|
|
Assert.Equal(2, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextIdentity(string databaseName, Action<ModelBuilder> modelBuilder) : ContextBase(databaseName, modelBuilder);
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_default_value_from_sequence()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(CreateBlog("One Unicorn"), CreateBlog("Two Unicorns"));
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(0, blogs[0].Id);
|
|
Assert.Equal(1, blogs[1].Id);
|
|
}
|
|
|
|
await using (var context = new BlogContextDefaultValueNoMigrations(testStore.Name, OnModelCreating))
|
|
{
|
|
context.AddRange(CreateBlog("One Unicorn"), CreateBlog("Two Unicorns"));
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextDefaultValueNoMigrations(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(0, blogs[0].Id);
|
|
Assert.Equal(1, blogs[1].Id);
|
|
Assert.Equal(2, blogs[2].Id);
|
|
Assert.Equal(3, blogs[3].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextDefaultValue(string databaseName, Action<ModelBuilder> modelBuilder) : ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.HasSequence("MySequence")
|
|
.StartsAt(0);
|
|
|
|
modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.HasDefaultValueSql("next value for MySequence");
|
|
}
|
|
}
|
|
|
|
public class BlogContextDefaultValueNoMigrations(string databaseName, Action<ModelBuilder> modelBuilder)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.HasDefaultValue();
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_default_string_value_from_sequence()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextStringDefaultValue(testStore.Name, OnModelCreating, StringSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new BlogWithStringKey { Id = StringSentinel, Name = "One Unicorn" },
|
|
new BlogWithStringKey { Id = StringSentinel, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextStringDefaultValue(testStore.Name, OnModelCreating, StringSentinel))
|
|
{
|
|
var blogs = context.StringyBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal("i77", blogs[0].Id);
|
|
Assert.Equal("i78", blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextStringDefaultValue(string databaseName, Action<ModelBuilder> modelBuilder, string stringSentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly string _stringSentinel = stringSentinel;
|
|
|
|
public DbSet<BlogWithStringKey> StringyBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.HasSequence("MyStringSequence")
|
|
.StartsAt(77);
|
|
|
|
modelBuilder
|
|
.Entity<BlogWithStringKey>()
|
|
.Property(e => e.Id)
|
|
.HasDefaultValueSql("'i' + CAST((NEXT VALUE FOR MyStringSequence) AS VARCHAR(20))")
|
|
.HasSentinel(_stringSentinel);
|
|
}
|
|
}
|
|
|
|
public class BlogWithStringKey
|
|
{
|
|
public string Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_key_default_value_from_sequence()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(CreateBlog("One Unicorn"), CreateBlog("Two Unicorns"));
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextKeyColumnWithDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(77, blogs[0].Id);
|
|
Assert.Equal(78, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextKeyColumnWithDefaultValue(string databaseName, Action<ModelBuilder> modelBuilder)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.HasSequence("MySequence")
|
|
.StartsAt(77);
|
|
|
|
modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.HasDefaultValueSql("next value for MySequence")
|
|
.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Throw);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_uint_to_Identity_column_using_value_converter()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name, OnModelCreating, UIntSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new BlogWithUIntKey { Id = UIntSentinel, Name = "One Unicorn" },
|
|
new BlogWithUIntKey { Id = UIntSentinel, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextUIntToIdentityUsingValueConverter(testStore.Name, OnModelCreating, UIntSentinel))
|
|
{
|
|
var blogs = context.UnsignedBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal((uint)1, blogs[0].Id);
|
|
Assert.Equal((uint)2, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextUIntToIdentityUsingValueConverter(string databaseName, Action<ModelBuilder> modelBuilder, uint uintSentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly uint _uintSentinel = uintSentinel;
|
|
|
|
public DbSet<BlogWithUIntKey> UnsignedBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<BlogWithUIntKey>()
|
|
.Property(e => e.Id)
|
|
.HasConversion<int>()
|
|
.HasSentinel(_uintSentinel);
|
|
}
|
|
}
|
|
|
|
public class BlogWithUIntKey
|
|
{
|
|
public uint Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_int_enum_to_Identity_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
using (var context = new BlogContextIntEnumToIdentity(testStore.Name, OnModelCreating, IntKeySentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new BlogWithIntEnumKey { Id = IntKeySentinel, Name = "One Unicorn" },
|
|
new BlogWithIntEnumKey { Id = IntKeySentinel, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
using (var context = new BlogContextIntEnumToIdentity(testStore.Name, OnModelCreating, IntKeySentinel))
|
|
{
|
|
var blogs = context.EnumBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(1, (int)blogs[0].Id);
|
|
Assert.Equal(2, (int)blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextIntEnumToIdentity(string databaseName, Action<ModelBuilder> modelBuilder, IntKey sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly IntKey _sentinel = sentinel;
|
|
|
|
public DbSet<BlogWithIntEnumKey> EnumBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<BlogWithIntEnumKey>()
|
|
.Property(e => e.Id)
|
|
.ValueGeneratedOnAdd()
|
|
.HasSentinel(_sentinel);
|
|
}
|
|
}
|
|
|
|
public class BlogWithIntEnumKey
|
|
{
|
|
public IntKey Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
public enum IntKey
|
|
{
|
|
Zero,
|
|
One,
|
|
SixSixSeven,
|
|
}
|
|
|
|
//Does not support ulong as identity
|
|
/*[ConditionalFact]
|
|
public async Task Insert_ulong_enum_to_Identity_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextULongEnumToIdentity(testStore.Name, OnModelCreating, ULongKeySentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new BlogWithULongEnumKey { Id = ULongKeySentinel, Name = "One Unicorn" },
|
|
new BlogWithULongEnumKey { Id = ULongKeySentinel, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextULongEnumToIdentity(testStore.Name, OnModelCreating, ULongKeySentinel))
|
|
{
|
|
var blogs = context.EnumBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(1, (int)blogs[0].Id);
|
|
Assert.Equal(2, (int)blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextULongEnumToIdentity(string databaseName, Action<ModelBuilder> modelBuilder, ULongKey sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly ULongKey _sentinel = sentinel;
|
|
|
|
public DbSet<BlogWithULongEnumKey> EnumBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<BlogWithULongEnumKey>()
|
|
.Property(e => e.Id)
|
|
.ValueGeneratedOnAdd()
|
|
.HasSentinel(_sentinel);
|
|
}
|
|
}
|
|
|
|
public class BlogWithULongEnumKey
|
|
{
|
|
public ULongKey Id { get; set; }
|
|
public string Name { get; set; }
|
|
}
|
|
|
|
public enum ULongKey : ulong
|
|
{
|
|
Zero,
|
|
Sentinel
|
|
}*/
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_string_to_Identity_column_using_value_converter()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name, OnModelCreating, StringSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new BlogWithStringKey { Id = StringSentinel, Name = "One Unicorn" },
|
|
new BlogWithStringKey { Id = StringSentinel, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextStringToIdentityUsingValueConverter(testStore.Name, OnModelCreating, StringSentinel))
|
|
{
|
|
var blogs = context.StringyBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal("1", blogs[0].Id);
|
|
Assert.Equal("2", blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextStringToIdentityUsingValueConverter(string databaseName, Action<ModelBuilder> modelBuilder, string sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly string _sentinel = sentinel;
|
|
|
|
public DbSet<BlogWithStringKey> StringyBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
Guid guid;
|
|
modelBuilder
|
|
.Entity<BlogWithStringKey>()
|
|
.Property(e => e.Id)
|
|
.HasValueGenerator<TemporaryStringValueGenerator>()
|
|
.HasConversion(
|
|
v => Guid.TryParse(v, out guid)
|
|
? default
|
|
: int.Parse(v),
|
|
v => v.ToString())
|
|
.ValueGeneratedOnAdd()
|
|
.HasSentinel(_sentinel);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_explicit_non_default_keys()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextNoKeyGeneration(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog { Id = 66, Name = "One Unicorn" }, new Blog { Id = 67, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextNoKeyGeneration(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(66, blogs[0].Id);
|
|
Assert.Equal(67, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextNoKeyGeneration(string databaseName, Action<ModelBuilder> modelBuilder)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.ValueGeneratedNever();
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_explicit_with_default_keys()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name, OnModelCreating, NullableIntSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new NullableKeyBlog { Id = 0, Name = "One Unicorn" },
|
|
new NullableKeyBlog { Id = 1, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextNoKeyGenerationNullableKey(testStore.Name, OnModelCreating, NullableIntSentinel))
|
|
{
|
|
var blogs = context.NullableKeyBlogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(0, blogs[0].Id);
|
|
Assert.Equal(1, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextNoKeyGenerationNullableKey(string databaseName, Action<ModelBuilder> modelBuilder, int? sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int? _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<NullableKeyBlog>()
|
|
.Property(e => e.Id)
|
|
.ValueGeneratedNever()
|
|
.HasSentinel(_sentinel);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_non_key_default_value()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
|
|
await using (var context = new BlogContextNonKeyDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blogs = new List<Blog>
|
|
{
|
|
new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "One Unicorn",
|
|
CreatedOn = DateTimeSentinel,
|
|
NeedsConverter = NeedsConverterSentinel
|
|
},
|
|
new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "Two Unicorns",
|
|
CreatedOn = new DateTime(1969, 8, 3, 0, 10, 0),
|
|
NeedsConverter = new NeedsConverter(111)
|
|
}
|
|
};
|
|
|
|
context.AddRange(blogs);
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.NotEqual(new DateTime(), blogs[0].CreatedOn);
|
|
Assert.NotEqual(new DateTime(), blogs[1].CreatedOn);
|
|
Assert.Equal(111, blogs[1].NeedsConverter.Value);
|
|
}
|
|
|
|
await using (var context = new BlogContextNonKeyDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Name).ToList();
|
|
Assert.Equal(3, blogs.Count);
|
|
|
|
Assert.NotEqual(new DateTime(), blogs[0].CreatedOn);
|
|
Assert.Equal(new DateTime(1969, 8, 3, 0, 10, 0), blogs[1].CreatedOn);
|
|
Assert.Equal(new DateTime(1974, 8, 3, 0, 10, 0), blogs[2].CreatedOn);
|
|
|
|
blogs[0].CreatedOn = new DateTime(1973, 9, 3, 0, 10, 0);
|
|
|
|
blogs[1].Name = "X Unicorns";
|
|
blogs[1].NeedsConverter = new NeedsConverter(222);
|
|
|
|
blogs[2].Name = "Y Unicorns";
|
|
blogs[2].NeedsConverter = new NeedsConverter(333);
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextNonKeyDefaultValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Name).ToList();
|
|
Assert.Equal(3, blogs.Count);
|
|
|
|
Assert.Equal(new DateTime(1973, 9, 3, 0, 10, 0), blogs[0].CreatedOn);
|
|
Assert.Equal(new DateTime(1969, 8, 3, 0, 10, 0), blogs[1].CreatedOn);
|
|
Assert.Equal(222, blogs[1].NeedsConverter.Value);
|
|
Assert.Equal(new DateTime(1974, 8, 3, 0, 10, 0), blogs[2].CreatedOn);
|
|
Assert.Equal(333, blogs[2].NeedsConverter.Value);
|
|
}
|
|
}
|
|
|
|
/*[ConditionalFact]
|
|
public async Task Insert_with_non_key_default_spatial_value()
|
|
{
|
|
using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
|
|
using (var context = new BlogContextNonKeyDefaultSpatialValue(testStore.Name, OnModelCreating))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blogs = new List<BlogWithSpatial>
|
|
{
|
|
new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "One Unicorn",
|
|
GeometryCollection = GeometryCollectionSentinel
|
|
},
|
|
new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "Two Unicorns",
|
|
GeometryCollection = GeometryFactory.CreateGeometryCollection(
|
|
[GeometryFactory.CreatePoint(new Coordinate(1, 3))])
|
|
}
|
|
};
|
|
|
|
context.AddRange(blogs);
|
|
|
|
context.SaveChanges();
|
|
|
|
var point = ((Point)blogs[1].GeometryCollection.Geometries[0]);
|
|
Assert.Equal(1, point.X);
|
|
Assert.Equal(3, point.Y);
|
|
}
|
|
|
|
using (var context = new BlogContextNonKeyDefaultSpatialValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.SpatialBlogs.OrderBy(e => e.Name).ToList();
|
|
Assert.Equal(3, blogs.Count);
|
|
|
|
var point1 = ((Point)blogs[1].GeometryCollection.Geometries[0]);
|
|
Assert.Equal(1, point1.X);
|
|
Assert.Equal(3, point1.Y);
|
|
|
|
var point2 = ((Point)blogs[2].GeometryCollection.Geometries[0]);
|
|
Assert.Equal(1, point2.X);
|
|
Assert.Equal(2, point2.Y);
|
|
|
|
blogs[1].GeometryCollection.Geometries[0] = GeometryFactory.CreatePoint(new Coordinate(1, 11));
|
|
blogs[2].GeometryCollection.Geometries[0] = GeometryFactory.CreatePoint(new Coordinate(1, 22));
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
using (var context = new BlogContextNonKeyDefaultSpatialValue(testStore.Name, OnModelCreating))
|
|
{
|
|
var blogs = context.SpatialBlogs.OrderBy(e => e.Name).ToList();
|
|
Assert.Equal(3, blogs.Count);
|
|
|
|
var point1 = ((Point)blogs[1].GeometryCollection.Geometries[0]);
|
|
Assert.Equal(1, point1.X);
|
|
Assert.Equal(11, point1.Y);
|
|
|
|
var point2 = ((Point)blogs[2].GeometryCollection.Geometries[0]);
|
|
Assert.Equal(1, point2.X);
|
|
Assert.Equal(22, point2.Y);
|
|
}
|
|
}*/
|
|
|
|
public class BlogContextNonKeyDefaultValue(string databaseName, Action<ModelBuilder> modelBuilder)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<Blog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.CreatedOn).HasDefaultValueSql("NOW()");
|
|
|
|
b.HasData(
|
|
new Blog
|
|
{
|
|
Id = 9979,
|
|
Name = "W Unicorns",
|
|
CreatedOn = new DateTime(1974, 8, 3, 0, 10, 0),
|
|
NeedsConverter = new NeedsConverter(111),
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
/*public class BlogContextNonKeyDefaultSpatialValue(string databaseName, Action<ModelBuilder> modelBuilder)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<BlogWithSpatial>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.GeometryCollection).HasDefaultValue(GeometryFactory.CreateGeometryCollection());
|
|
|
|
b.HasData(
|
|
new BlogWithSpatial
|
|
{
|
|
Id = 9979,
|
|
Name = "W Unicorns",
|
|
GeometryCollection = GeometryFactory.CreateGeometryCollection(
|
|
[GeometryFactory.CreatePoint(new Coordinate(1, 2))])
|
|
});
|
|
});
|
|
}
|
|
}*/
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_non_key_default_value_readonly()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name, OnModelCreating, IntSentinel, DateTimeSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "One Unicorn",
|
|
CreatedOn = DateTimeSentinel
|
|
},
|
|
new Blog
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "Two Unicorns",
|
|
CreatedOn = DateTimeSentinel
|
|
});
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.NotEqual(new DateTime(), context.Blogs.ToList()[0].CreatedOn);
|
|
}
|
|
|
|
DateTime dateTime0;
|
|
|
|
using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name, OnModelCreating, IntSentinel, DateTimeSentinel))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
dateTime0 = blogs[0].CreatedOn;
|
|
|
|
Assert.NotEqual(new DateTime(), dateTime0);
|
|
Assert.NotEqual(new DateTime(), blogs[1].CreatedOn);
|
|
|
|
blogs[0].Name = "One Pegasus";
|
|
blogs[1].CreatedOn = new DateTime(1973, 9, 3, 0, 10, 0);
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
using (var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name, OnModelCreating, IntSentinel, DateTimeSentinel))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(dateTime0, blogs[0].CreatedOn);
|
|
Assert.Equal(new DateTime(1973, 9, 3, 0, 10, 0), blogs[1].CreatedOn);
|
|
}
|
|
}
|
|
|
|
public class BlogContextNonKeyReadOnlyDefaultValue(
|
|
string databaseName,
|
|
Action<ModelBuilder> modelBuilder,
|
|
int intSentinel,
|
|
DateTime dateTimeSentinel) : ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _intSentinel = intSentinel;
|
|
private readonly DateTime _dateTimeSentinel = dateTimeSentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<Blog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.Id).HasSentinel(_intSentinel);
|
|
|
|
var property = b.Property(e => e.CreatedOn).HasDefaultValueSql("NOW()").HasSentinel(_dateTimeSentinel);
|
|
property.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Throw);
|
|
});
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_and_update_with_computed_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
using (var context = new BlogContextComputedColumn(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blog = context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "One",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
}).Entity;
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("One Unicorn", blog.FullName);
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumn(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.FullNameBlogs.Single();
|
|
|
|
Assert.Equal("One Unicorn", blog.FullName);
|
|
|
|
blog.LastName = "Pegasus";
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("One Pegasus", blog.FullName);
|
|
}
|
|
}
|
|
|
|
public class BlogContextComputedColumn(string databaseName, Action<ModelBuilder> modelBuilder, int intSentinel, string stringSentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _intSentinel = intSentinel;
|
|
private readonly string _stringSentinel = stringSentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<FullNameBlog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.Id).HasSentinel(_intSentinel);
|
|
|
|
var property = b.Property(e => e.FullName)
|
|
.HasComputedColumnSql("FirstName + ' ' + LastName")
|
|
.HasSentinel(_stringSentinel)
|
|
.Metadata;
|
|
|
|
property.SetBeforeSaveBehavior(PropertySaveBehavior.Throw);
|
|
property.SetAfterSaveBehavior(PropertySaveBehavior.Throw);
|
|
});
|
|
}
|
|
}
|
|
|
|
public class BlogContextComputedColumnWithTriggerMetadata(
|
|
string databaseName,
|
|
Action<ModelBuilder> modelBuilder,
|
|
int intSentinel,
|
|
string stringSentinel) : BlogContextComputedColumn(databaseName, modelBuilder, intSentinel, stringSentinel)
|
|
{
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<FullNameBlog>().ToTable(tb => tb.HasTrigger("SomeTrigger"));
|
|
}
|
|
}
|
|
|
|
/*// #6044
|
|
[ConditionalFact]
|
|
public async Task Insert_and_update_with_computed_column_with_function()
|
|
{
|
|
using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
using (var context = new BlogContextComputedColumnWithFunction(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
context.Database.ExecuteSqlRaw
|
|
(
|
|
@"CREATE FUNCTION
|
|
[dbo].[GetFullName](@First NVARCHAR(MAX), @Second NVARCHAR(MAX))
|
|
RETURNS NVARCHAR(MAX) WITH SCHEMABINDING AS BEGIN RETURN @First + @Second END");
|
|
|
|
context.GetService<IRelationalDatabaseCreator>().CreateTables();
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumnWithFunction(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "One",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
}).Entity;
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("OneUnicorn", blog.FullName);
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumnWithFunction(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.FullNameBlogs.Single();
|
|
|
|
Assert.Equal("OneUnicorn", blog.FullName);
|
|
|
|
blog.LastName = "Pegasus";
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("OnePegasus", blog.FullName);
|
|
}
|
|
}
|
|
|
|
public class BlogContextComputedColumnWithFunction(
|
|
string databaseName,
|
|
Action<ModelBuilder> modelBuilder,
|
|
int intSentinel,
|
|
string stringSentinel) : ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _intSentinel = intSentinel;
|
|
private readonly string _stringSentinel = stringSentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<FullNameBlog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.Id).HasSentinel(_intSentinel);
|
|
|
|
var property = modelBuilder.Entity<FullNameBlog>()
|
|
.Property(e => e.FullName)
|
|
.HasComputedColumnSql("[dbo].[GetFullName]([FirstName], [LastName])")
|
|
.HasSentinel(_stringSentinel)
|
|
.Metadata;
|
|
|
|
property.SetAfterSaveBehavior(PropertySaveBehavior.Throw);
|
|
});
|
|
}
|
|
}
|
|
|
|
// #6044
|
|
[ConditionalFact]
|
|
public async Task Insert_and_update_with_computed_column_with_querying_function()
|
|
{
|
|
JetTestStore testStore = null;
|
|
try
|
|
{
|
|
testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
using (var context = new BlogContextComputedColumnWithTriggerMetadata(
|
|
testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
context.GetService<IRelationalDatabaseCreator>().CreateTables();
|
|
|
|
context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs DROP COLUMN FullName;");
|
|
|
|
context.Database.ExecuteSqlRaw(
|
|
@"CREATE FUNCTION [dbo].[GetFullName](@Id int)
|
|
RETURNS nvarchar(max) WITH SCHEMABINDING AS
|
|
BEGIN
|
|
DECLARE @FullName nvarchar(max);
|
|
SELECT @FullName = [FirstName] + [LastName] FROM [dbo].[FullNameBlogs] WHERE [Id] = @Id;
|
|
RETURN @FullName
|
|
END");
|
|
|
|
context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs ADD FullName AS [dbo].[GetFullName]([Id]); ");
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumnWithTriggerMetadata(
|
|
testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "One",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
}).Entity;
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("OneUnicorn", blog.FullName);
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumnWithTriggerMetadata(
|
|
testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.FullNameBlogs.Single();
|
|
|
|
Assert.Equal("OneUnicorn", blog.FullName);
|
|
|
|
blog.LastName = "Pegasus";
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("OnePegasus", blog.FullName);
|
|
}
|
|
|
|
using (var context = new BlogContextComputedColumnWithTriggerMetadata(
|
|
testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog1 = context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "Hank",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
}).Entity;
|
|
|
|
var blog2 = context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "Jeff",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
}).Entity;
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.Equal("HankUnicorn", blog1.FullName);
|
|
Assert.Equal("JeffUnicorn", blog2.FullName);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
using var context = new BlogContextComputedColumnWithTriggerMetadata(
|
|
DatabaseName, OnModelCreating, IntSentinel, StringSentinel);
|
|
context.Database.ExecuteSqlRaw("ALTER TABLE dbo.FullNameBlogs DROP COLUMN FullName;");
|
|
context.Database.ExecuteSqlRaw("DROP FUNCTION [dbo].[GetFullName];");
|
|
testStore?.Dispose();
|
|
}
|
|
}*/
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_client_generated_GUID_key()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
Guid afterSave;
|
|
await using (var context = new BlogContextClientGuidKey(testStore.Name, OnModelCreating, GuidSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blog = context.Add(
|
|
new GuidBlog
|
|
{
|
|
Id = GuidSentinel,
|
|
Name = "One Unicorn",
|
|
NotId = GuidSentinel
|
|
}).Entity;
|
|
|
|
var beforeSave = blog.Id;
|
|
var beforeSaveNotId = blog.NotId;
|
|
|
|
Assert.NotEqual(default, beforeSave);
|
|
Assert.NotEqual(default, beforeSaveNotId);
|
|
|
|
context.SaveChanges();
|
|
|
|
afterSave = blog.Id;
|
|
var afterSaveNotId = blog.NotId;
|
|
|
|
Assert.Equal(beforeSave, afterSave);
|
|
Assert.Equal(beforeSaveNotId, afterSaveNotId);
|
|
}
|
|
|
|
await using (var context = new BlogContextClientGuidKey(testStore.Name, OnModelCreating, GuidSentinel))
|
|
{
|
|
Assert.Equal(afterSave, context.GuidBlogs.Single().Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextClientGuidKey(string databaseName, Action<ModelBuilder> modelBuilder, Guid sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly Guid _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<GuidBlog>(
|
|
eb =>
|
|
{
|
|
eb.HasAlternateKey(e => e.NotId);
|
|
eb.Property(e => e.NotId).ValueGeneratedOnAdd().HasSentinel(_sentinel);
|
|
eb.Property(e => e.Id).HasSentinel(_sentinel);
|
|
});
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_ValueGeneratedOnAdd_GUID_nonkey_property_throws()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContextClientGuidNonKey(testStore.Name, OnModelCreating, GuidSentinel);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blog = context.Add(
|
|
new GuidBlog
|
|
{
|
|
Id = GuidSentinel,
|
|
Name = "One Unicorn",
|
|
NotId = GuidSentinel
|
|
}).Entity;
|
|
|
|
Assert.Equal(GuidSentinel, blog.NotId);
|
|
|
|
// No value set on a required column
|
|
var updateException = Assert.Throws<DbUpdateException>(() => context.SaveChanges());
|
|
Assert.Single(updateException.Entries);
|
|
}
|
|
|
|
public class BlogContextClientGuidNonKey(string databaseName, Action<ModelBuilder> modelBuilder, Guid sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly Guid _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<GuidBlog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.Id).HasSentinel(_sentinel);
|
|
b.Property(e => e.NotId).ValueGeneratedOnAdd().HasSentinel(_sentinel);
|
|
});
|
|
}
|
|
}
|
|
|
|
[ConditionalFact(Skip = "Jet can't return server generated guid's when it is the key. Currently using client geerated guid's")]
|
|
public async Task Insert_with_server_generated_GUID_key()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
Guid afterSave;
|
|
await using (var context = new BlogContextServerGuidKey(testStore.Name, OnModelCreating, GuidSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blog = context.Add(
|
|
new GuidBlog
|
|
{
|
|
Id = GuidSentinel,
|
|
Name = "One Unicorn",
|
|
NotId = GuidSentinel
|
|
}).Entity;
|
|
|
|
var beforeSave = blog.Id;
|
|
var beforeSaveNotId = blog.NotId;
|
|
|
|
Assert.Equal(GuidSentinel, beforeSave);
|
|
Assert.Equal(GuidSentinel, beforeSaveNotId);
|
|
|
|
context.SaveChanges();
|
|
|
|
afterSave = blog.Id;
|
|
var afterSaveNotId = blog.NotId;
|
|
|
|
Assert.NotEqual(GuidSentinel, afterSave);
|
|
Assert.NotEqual(GuidSentinel, afterSaveNotId);
|
|
Assert.NotEqual(beforeSave, afterSave);
|
|
Assert.NotEqual(beforeSaveNotId, afterSaveNotId);
|
|
}
|
|
|
|
using (var context = new BlogContextServerGuidKey(testStore.Name, OnModelCreating, GuidSentinel))
|
|
{
|
|
Assert.Equal(afterSave, context.GuidBlogs.Single().Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextServerGuidKey(string databaseName, Action<ModelBuilder> modelBuilder, Guid sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly Guid _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<GuidBlog>(
|
|
eb =>
|
|
{
|
|
eb.Property(e => e.Id).HasDefaultValueSql("GenGUID()").HasSentinel(_sentinel);
|
|
eb.Property(e => e.NotId).HasDefaultValueSql("GenGUID()").HasSentinel(_sentinel);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Negative cases
|
|
[ConditionalFact]
|
|
public async Task Insert_with_explicit_non_default_keys_by_default()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContext(testStore.Name, OnModelCreating);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog { Id = 1, Name = "One Unicorn" }, new Blog { Id = 2, Name = "Two Unicorns" });
|
|
|
|
// DbUpdateException : An error occurred while updating the entries. See the
|
|
// inner exception for details.
|
|
// SqlException : Cannot insert explicit value for identity column in table
|
|
// 'Blog' when IDENTITY_INSERT is set to OFF.
|
|
context.Database.CreateExecutionStrategy().Execute(context, c => Assert.Throws<DbUpdateException>(() => c.SaveChanges()));
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_explicit_default_keys()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContext(testStore.Name, OnModelCreating);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog { Id = IntSentinel, Name = "One Unicorn" }, new Blog { Id = 1, Name = "Two Unicorns" });
|
|
|
|
// DbUpdateException : An error occurred while updating the entries. See the
|
|
// inner exception for details.
|
|
// SqlException : Cannot insert explicit value for identity column in table
|
|
// 'Blog' when IDENTITY_INSERT is set to OFF.
|
|
var updateException = Assert.Throws<DbUpdateException>(() => context.SaveChanges());
|
|
Assert.Single(updateException.Entries);
|
|
}
|
|
|
|
public class BlogContext(string databaseName, Action<ModelBuilder> modelBuilder) : ContextBase(databaseName, modelBuilder);
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_with_implicit_default_keys()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextSpecifyKeysUsingDefault(testStore.Name, OnModelCreating, IntSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog { Id = 0, Name = "One Unicorn" }, new Blog { Id = 667, Name = "Two Unicorns" });
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextSpecifyKeysUsingDefault(testStore.Name, OnModelCreating, IntSentinel))
|
|
{
|
|
var blogs = context.Blogs.OrderBy(e => e.Id).ToList();
|
|
|
|
Assert.Equal(0, blogs[0].Id);
|
|
Assert.Equal(667, blogs[1].Id);
|
|
}
|
|
}
|
|
|
|
public class BlogContextSpecifyKeysUsingDefault(string databaseName, Action<ModelBuilder> modelBuilder, int sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.ValueGeneratedNever()
|
|
.HasSentinel(_sentinel);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_explicit_value_throws_when_readonly_sequence_before_save()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContextReadOnlySequenceKeyColumnWithDefaultValue(testStore.Name, OnModelCreating, IntSentinel);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog { Id = 1, Name = "One Unicorn" }, new Blog { Id = IntSentinel, Name = "Two Unicorns" });
|
|
|
|
// The property 'Id' on entity type 'Blog' is defined to be read-only before it is
|
|
// saved, but its value has been set to something other than a temporary or default value.
|
|
Assert.Equal(
|
|
CoreStrings.PropertyReadOnlyBeforeSave("Id", "Blog"),
|
|
Assert.Throws<InvalidOperationException>(() => context.SaveChanges()).Message);
|
|
}
|
|
|
|
public class BlogContextReadOnlySequenceKeyColumnWithDefaultValue(string databaseName, Action<ModelBuilder> modelBuilder, int sentinel)
|
|
: ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _sentinel = sentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.HasSequence("MySequence");
|
|
|
|
var property = modelBuilder
|
|
.Entity<Blog>()
|
|
.Property(e => e.Id)
|
|
.HasDefaultValueSql("next value for MySequence")
|
|
.HasSentinel(_sentinel);
|
|
|
|
property.Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Throw);
|
|
}
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_explicit_value_throws_when_readonly_before_save()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContextNonKeyReadOnlyDefaultValue(testStore.Name, OnModelCreating, IntSentinel, DateTimeSentinel);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.AddRange(
|
|
new Blog
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "One Unicorn",
|
|
CreatedOn = DateTimeSentinel
|
|
},
|
|
new Blog
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "Two Unicorns",
|
|
CreatedOn = new DateTime(1973, 8, 3, 0, 10, 0)
|
|
});
|
|
|
|
// The property 'CreatedOn' on entity type 'Blog' is defined to be read-only before it is
|
|
// saved, but its value has been set to something other than a temporary or default value.
|
|
Assert.Equal(
|
|
CoreStrings.PropertyReadOnlyBeforeSave("CreatedOn", "Blog"),
|
|
Assert.Throws<InvalidOperationException>(() => context.SaveChanges()).Message);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Insert_explicit_value_into_computed_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContextComputedColumn(testStore.Name, OnModelCreating, IntSentinel, StringSentinel);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "One",
|
|
LastName = "Unicorn",
|
|
FullName = "Gerald"
|
|
});
|
|
|
|
// The property 'FullName' on entity type 'FullNameBlog' is defined to be read-only before it is
|
|
// saved, but its value has been set to something other than a temporary or default value.
|
|
Assert.Equal(
|
|
CoreStrings.PropertyReadOnlyBeforeSave("FullName", "FullNameBlog"),
|
|
Assert.Throws<InvalidOperationException>(() => context.SaveChanges()).Message);
|
|
}
|
|
|
|
[ConditionalFact]
|
|
public async Task Update_explicit_value_in_computed_column()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using (var context = new BlogContextComputedColumn(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
context.Add(
|
|
new FullNameBlog
|
|
{
|
|
Id = IntSentinel,
|
|
FirstName = "One",
|
|
LastName = "Unicorn",
|
|
FullName = StringSentinel
|
|
});
|
|
|
|
context.SaveChanges();
|
|
}
|
|
|
|
await using (var context = new BlogContextComputedColumn(testStore.Name, OnModelCreating, IntSentinel, StringSentinel))
|
|
{
|
|
var blog = context.FullNameBlogs.Single();
|
|
|
|
blog.FullName = "The Gorilla";
|
|
|
|
// The property 'FullName' on entity type 'FullNameBlog' is defined to be read-only after it has been saved,
|
|
// but its value has been modified or marked as modified.
|
|
Assert.Equal(
|
|
CoreStrings.PropertyReadOnlyAfterSave("FullName", "FullNameBlog"),
|
|
Assert.Throws<InvalidOperationException>(() => context.SaveChanges()).Message);
|
|
}
|
|
}
|
|
|
|
// Concurrency
|
|
[ConditionalFact]
|
|
public async Task Resolve_concurrency()
|
|
{
|
|
await using var testStore = await JetTestStore.CreateInitializedAsync(DatabaseName);
|
|
await using var context = new BlogContextConcurrencyWithRowversion(testStore.Name, OnModelCreating, IntSentinel, TimestampSentinel);
|
|
context.Database.EnsureCreatedResiliently();
|
|
|
|
var blog = context.Add(
|
|
new ConcurrentBlog
|
|
{
|
|
Id = IntSentinel,
|
|
Name = "One Unicorn",
|
|
Timestamp = TimestampSentinel
|
|
}).Entity;
|
|
|
|
context.SaveChanges();
|
|
|
|
await using var innerContext = new BlogContextConcurrencyWithRowversion(testStore.Name, OnModelCreating, IntSentinel, TimestampSentinel);
|
|
var updatedBlog = innerContext.ConcurrentBlogs.Single();
|
|
updatedBlog.Name = "One Pegasus";
|
|
innerContext.SaveChanges();
|
|
var currentTimestamp = updatedBlog.Timestamp.ToArray();
|
|
|
|
try
|
|
{
|
|
blog.Name = "One Earth Pony";
|
|
context.SaveChanges();
|
|
}
|
|
catch (DbUpdateConcurrencyException)
|
|
{
|
|
// Update original values (and optionally any current values)
|
|
// Would normally do this with just one method call
|
|
context.Entry(blog).Property(e => e.Id).OriginalValue = updatedBlog.Id;
|
|
context.Entry(blog).Property(e => e.Name).OriginalValue = updatedBlog.Name;
|
|
context.Entry(blog).Property(e => e.Timestamp).OriginalValue = updatedBlog.Timestamp;
|
|
|
|
context.SaveChanges();
|
|
|
|
Assert.NotEqual(blog.Timestamp, currentTimestamp);
|
|
}
|
|
}
|
|
|
|
public class BlogContextConcurrencyWithRowversion(
|
|
string databaseName,
|
|
Action<ModelBuilder> modelBuilder,
|
|
int intSentinel,
|
|
byte[] timestampSentinel) : ContextBase(databaseName, modelBuilder)
|
|
{
|
|
private readonly int _intSentinel = intSentinel;
|
|
private readonly byte[] _timestampSentinel = timestampSentinel;
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
modelBuilder.Entity<ConcurrentBlog>(
|
|
b =>
|
|
{
|
|
b.Property(e => e.Id).HasSentinel(_intSentinel);
|
|
b.Property(e => e.Timestamp)
|
|
.ValueGeneratedOnAddOrUpdate()
|
|
.IsConcurrencyToken()
|
|
.HasSentinel(_timestampSentinel);
|
|
});
|
|
}
|
|
}
|
|
|
|
protected Blog CreateBlog(string name)
|
|
=> new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = name,
|
|
CreatedOn = DateTimeSentinel,
|
|
NeedsConverter = NeedsConverterSentinel,
|
|
OtherId = NullableIntSentinel
|
|
};
|
|
|
|
public class Blog
|
|
{
|
|
public int Id { get; set; }
|
|
public string Name { get; set; }
|
|
public DateTime CreatedOn { get; set; }
|
|
public NeedsConverter NeedsConverter { get; set; }
|
|
public int? OtherId { get; set; }
|
|
}
|
|
|
|
/*protected BlogWithSpatial CreateBlogWithSpatial(string name)
|
|
=> new()
|
|
{
|
|
Id = IntSentinel,
|
|
Name = name,
|
|
GeometryCollection = GeometryCollectionSentinel,
|
|
};
|
|
|
|
public class BlogWithSpatial
|
|
{
|
|
public int Id { get; set; }
|
|
public string Name { get; set; }
|
|
public GeometryCollection GeometryCollection { get; set; }
|
|
}*/
|
|
|
|
public class NeedsConverter(int value)
|
|
{
|
|
public int Value { get; } = value;
|
|
|
|
public override bool Equals(object obj)
|
|
=> throw new InvalidOperationException();
|
|
|
|
public override int GetHashCode()
|
|
=> throw new InvalidOperationException();
|
|
}
|
|
|
|
public class NullableKeyBlog
|
|
{
|
|
public int? Id { get; set; }
|
|
public string Name { get; set; }
|
|
public DateTime CreatedOn { get; set; }
|
|
}
|
|
|
|
public class FullNameBlog
|
|
{
|
|
public int Id { get; set; }
|
|
public string FirstName { get; set; }
|
|
public string LastName { get; set; }
|
|
public string FullName { get; set; }
|
|
}
|
|
|
|
public class GuidBlog
|
|
{
|
|
public Guid Id { get; set; }
|
|
public string Name { get; set; }
|
|
public Guid NotId { get; set; }
|
|
}
|
|
|
|
public class ConcurrentBlog
|
|
{
|
|
public int Id { get; set; }
|
|
public string Name { get; set; }
|
|
public byte[] Timestamp { get; set; }
|
|
}
|
|
|
|
protected virtual void OnModelCreating(ModelBuilder modelBuilder)
|
|
=> modelBuilder.Entity<Blog>()
|
|
.Property(e => e.NeedsConverter)
|
|
.HasConversion(
|
|
v => v.Value,
|
|
v => new NeedsConverter(v),
|
|
new ValueComparer<NeedsConverter>(
|
|
(l, r) => (l == null && r == null) || (l != null && r != null && l.Value == r.Value),
|
|
v => v.Value.GetHashCode(),
|
|
v => new NeedsConverter(v.Value)))
|
|
.HasDefaultValue(new NeedsConverter(999));
|
|
|
|
public abstract class ContextBase(string databaseName, Action<ModelBuilder> builder) : DbContext
|
|
{
|
|
public DbSet<Blog> Blogs { get; set; }
|
|
//public DbSet<BlogWithSpatial> SpatialBlogs { get; set; }
|
|
public DbSet<NullableKeyBlog> NullableKeyBlogs { get; set; }
|
|
public DbSet<FullNameBlog> FullNameBlogs { get; set; }
|
|
public DbSet<GuidBlog> GuidBlogs { get; set; }
|
|
public DbSet<ConcurrentBlog> ConcurrentBlogs { get; set; }
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
=> builder(modelBuilder);
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
=> optionsBuilder
|
|
.EnableServiceProviderCaching(false)
|
|
.UseJet(
|
|
JetTestStore.CreateConnectionString(databaseName),
|
|
b => b.ApplyConfiguration());
|
|
}
|
|
|
|
public static IEnumerable<object[]> IsAsyncData = [[false], [true]];
|
|
}
|