Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BulkInsert ignore value conversion of owned entities if that conversion configured by datatype itself instead of ValueConverter. #895

Open
bangjiehan opened this issue Jul 21, 2022 · 0 comments

Comments

@bangjiehan
Copy link

bangjiehan commented Jul 21, 2022

I found BulkInsert ignore value conversion when it handle owned entities. Seems version 6.5.3 had fixed a part of this problem. In 6.5.3 and above BulkInsert will take custom value conversion in account but EF have some built-in value conversion that can be used by Type directly and BulkInsert still ignore it. Although we can use explicit value conversion, it will be good that BulkInsert try to be consistent with EF.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="EFCore.BulkExtensions" Version="6.5.4" />
    <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.7" />
  </ItemGroup>

</Project>
using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System.Diagnostics;

class Simple
{
    public int Id { get; set; }
    public DateTime DateTime { get; set; }
    public Guid Guid { get; set; }
}

class Parent
{
    public int Id { get; set; }
    public Child Child { get; set; } = null!;
}

class Child
{
    public DateTime DateTime { get; set; }
    public Guid Guid { get; set; }
}

class UnixTimeConverter : ValueConverter<DateTime, long>
{
    public UnixTimeConverter() : base(x => new DateTimeOffset(x).ToUnixTimeSeconds(), x => DateTimeOffset.FromUnixTimeSeconds(x).DateTime)
    {
    }
}

class GuidConverter : ValueConverter<Guid, byte[]>
{
    public GuidConverter() : base(x => x.ToByteArray(), x => new Guid(x))
    {
    }
}

class Context : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlite("Data Source=sqlite.db");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Simple>(e =>
        {
            e.Property(x => x.DateTime).HasConversion<UnixTimeConverter>();
            e.Property(x => x.Guid).HasConversion<byte[]>();
        });
        modelBuilder.Entity<Parent>()
                    .OwnsOne(x => x.Child, e =>
                    {
                        e.Property(x => x.DateTime).HasConversion<UnixTimeConverter>();
                        e.Property(x => x.Guid).HasConversion<byte[]>();
                        //e.Property(x => x.Guid).HasConversion<GuidConverter>(); // It works.
                    });
    }
}

class Program
{
    public static void Main()
    {
        using (var context = new Context())
        {
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            DateTime dateTime = DateTime.Parse("1970-01-01T00:00:01+0");
            Guid guid = Guid.Empty;
            Simple simple = new() { DateTime = dateTime, Guid = guid };
            Parent parent = new() { Child = new() { DateTime = dateTime, Guid = guid } };
            bool use_bulk_insert = true;
            if (use_bulk_insert)
            {
                context.BulkInsert(new Simple[] { simple });
                context.BulkInsert(new Parent[] { parent });
            }
            else
            {
                context.Set<Simple>().Add(simple);
                context.Set<Parent>().Add(parent);
                context.SaveChanges();
            }

            var connection = context.Database.GetDbConnection();
            var command = connection.CreateCommand();
            connection.Open();

            command.CommandText = $"SELECT {nameof(Simple.DateTime)}, {nameof(Simple.Guid)} FROM {nameof(Simple)} WHERE rowid=1";
            var reader_simple = command.ExecuteReader();
            reader_simple.Read();
            var date_simple = reader_simple.GetString(0);
            var guid_simple = reader_simple.GetString(1);
            reader_simple.Close();
            Debug.Assert(date_simple == "1");
            Debug.Assert(guid_simple == new string('\0', 16));

            command.CommandText = $"SELECT {nameof(Parent.Child)}_{nameof(Parent.Child.DateTime)}, {nameof(Parent.Child)}_{nameof(Parent.Child.Guid)} FROM {nameof(Parent)} WHERE rowid=1";
            var reader_owned = command.ExecuteReader();
            reader_owned.Read();
            var date_owned = reader_owned.GetString(0);
            var guid_owned = reader_owned.GetString(1);
            reader_owned.Close();
            Debug.Assert(date_owned == "1"); // Fail when version of EFCore.BulkExtensions <= 6.5.2
            Debug.Assert(guid_owned == new string('\0', 16)); // Fail if we use 'HasConversion<byte[]>()' instead 'HasConversion<GuidConverter>()' on line 57-58

            connection.Close();
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants