Aracılığıyla paylaş


EF Core 8'de (EF8) hataya neden olan değişiklikler

Bu sayfada, EF Core 7'den EF Core 8'e güncelleştirilen mevcut uygulamaların bozulmasına neden olabilecek API ve davranış değişiklikleri belgelenmiştir. EF Core'un önceki bir sürümünden güncelleştirme yapıyorsanız, önceki hataya neden olan değişiklikleri gözden geçirmeyi unutmayın:

Hedef Çerçeve

EF Core 8, .NET 8'i hedefler. Eski .NET, .NET Core ve .NET Framework sürümlerini hedefleyen uygulamaların .NET 8'i hedefleyecek şekilde güncelleştirilmeleri gerekir.

Özet

Hataya neden olan değişiklik Etki
Contains LINQ sorguları eski SQL Server sürümlerinde çalışmayı durdurabilir Yüksek
JSON'daki sabit listeleri varsayılan olarak dizeler yerine int olarak depolanır Yüksek
SQL Server date ve time şimdi .NET DateOnly ve TimeOnly Orta
Veritabanı tarafından oluşturulan değere sahip Boole sütunları artık null atanabilir olarak yapı iskelesi oluşturulmuyor Orta
SQLite Math yöntemleri artık SQL'e çevrilir Düşük
ITypeBase bazı API'lerde IEntityType'ın yerini alır Düşük
ValueGenerator ifadeleri genel API'leri kullanmalıdır Düşük
ExcludeFromMigrations artık TPC hiyerarşisindeki diğer tabloları dışlamıyor Düşük
Gölge olmayan tamsayı anahtarları Cosmos belgelerinde kalıcı hale getiriliyor Düşük
İlişkisel model derlenmiş modelde oluşturulur Düşük
yapı iskelesi farklı gezinti adları oluşturabilir Düşük
Ayrımcıların artık uzunluk üstleri Düşük
SQL Server anahtar değerleri büyük/küçük harfe duyarsız olarak karşılaştırılır Düşük

Yüksek etkili değişiklikler

Contains LINQ sorguları eski SQL Server sürümlerinde çalışmayı durdurabilir

İzleme Sorunu #13617

Eski davranış

Contains Daha önce işleç parametreli değer listesiyle LINQ sorgularında kullanıldığında EF, verimsiz olan ancak tüm SQL Server sürümlerinde çalışan SQL'i oluşturmuştu.

Yeni davranış

EF Core 8.0'dan başlayarak, EF artık daha verimli, ancak SQL Server 2014 ve altında desteklenmeyen SQL oluşturur.

Daha yeni SQL Server sürümlerinin daha eski bir uyumluluk düzeyiyle yapılandırılarak yeni SQL ile uyumsuz hale getirilebileceğini unutmayın. Bu durum, eski uyumluluk düzeyini taşıyan önceki bir şirket içi SQL Server örneğinden geçirilen bir Azure SQL veritabanıyla da oluşabilir.

Neden?

EF Core tarafından oluşturulan önceki SQL, Contains parametreli değerleri SQL'e sabit olarak eklemiş. Örneğin, aşağıdaki LINQ sorgusu:

var names = new[] { "Blog1", "Blog2" };

var blogs = await context.Blogs
    .Where(b => names.Contains(b.Name))
    .ToArrayAsync();

... aşağıdaki SQL'e çevrilebilir:

SELECT [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] IN (N'Blog1', N'Blog2')

Sql'e sabit değerlerin eklenmesi, sorgu planını önbelleğe almayı bozan ve diğer sorguların gereksiz çıkarmalarına neden olan birçok performans sorununa neden olur. Yeni EF Core 8.0 çevirisi, değerleri JSON dizisi olarak aktarmak için SQL Server OPENJSON işlevini kullanır. Bu, önceki teknikte bulunan performans sorunlarını çözer; ancak işlev OPENJSON SQL Server 2014 ve altında kullanılamaz.

Bu değişiklik hakkında daha fazla bilgi için bu blog gönderisini inceleyin.

Risk Azaltıcı Etkenler

Veritabanınız SQL Server 2016 (13.x) veya daha yeniyse ya da Azure SQL kullanıyorsanız aşağıdaki komutu kullanarak veritabanınızın yapılandırılmış uyumluluk düzeyini denetleyin:

SELECT name, compatibility_level FROM sys.databases;

Uyumluluk düzeyi 130'un altındaysa (SQL Server 2016), bunu daha yeni bir değerle (belgeler) değiştirmeyi göz önünde bulundurun.

Aksi takdirde, veritabanı sürümünüz SQL Server 2016'dan gerçekten daha eskiyse veya bazı nedenlerden dolayı değiştiremeyeceğiniz eski bir uyumluluk düzeyine ayarlanmışsa EF Core'ı aşağıdaki gibi daha eski, daha az verimli SQL'e geri dönecek şekilde yapılandırın:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseSqlServer(@"<CONNECTION STRING>", o => o.UseCompatibilityLevel(120));

JSON'daki sabit listeleri varsayılan olarak dizeler yerine int olarak depolanır

İzleme Sorunu #13617

Eski davranış

EF7'de, JSON ile eşlenen sabit listeleri varsayılan olarak JSON belgesinde dize değerleri olarak depolanır.

Yeni davranış

EF Core 8.0'dan başlayarak, EF varsayılan olarak, sabit listeleri JSON belgesindeki tamsayı değerleriyle eşler.

Neden?

EF her zaman varsayılan olarak ilişkisel veritabanlarındaki sayısal bir sütuna eşlenmiş sabit listeleri vardır. EF, JSON değerlerinin sütunlardan ve parametrelerden gelen değerlerle etkileşimde bulunduğu sorguları desteklediğinden, JSON'daki değerlerin JSON olmayan sütundaki değerlerle eşleşmesi önemlidir.

Risk Azaltıcı Etkenler

Dizeleri kullanmaya devam etmek için enum özelliğini bir dönüştürme ile yapılandırın. Örneğin:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<User>().Property(e => e.Status).HasConversion<string>();
}

Veya sabit listesi türünün tüm özellikleri için:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Properties<StatusEnum>().HaveConversion<string>();
}

Orta düzeyde etki eden değişiklikler

SQL Server date ve time şimdi .NET DateOnly ve TimeOnly

İzleme Sorunu #24507

Eski davranış

Daha önce, SQL Server veritabanının time veya sütunlarının date iskelesini oluştururken EF, ve TimeSpantürlerine DateTime sahip varlık özellikleri oluştururdu.

Yeni davranış

EF Core 8.0 date ile başlayarak ve time olarak DateOnly TimeOnlyyapı iskelesi oluşturulur.

Neden?

DateOnly ve TimeOnly .NET 6.0 ile kullanıma sunulmuştur ve veritabanı tarih ve saat türlerini eşlemek için mükemmel bir eşleşmedir. DateTime özellikle kullanılmayan bir zaman bileşeni içerir ve bunu dateile eşlerken karışıklığa neden olabilir ve TimeSpan bir olayın gerçekleştiği günün saati yerine bir zaman aralığını (büyük olasılıkla günler de dahil) temsil eder. Yeni türlerin kullanılması hataları ve karışıklığı önler ve amacın netliğini sağlar.

Risk Azaltıcı Etkenler

Bu değişiklik yalnızca veritabanlarını düzenli olarak bir EF kod modeline ("önce veritabanı" akışı) yeniden iskeleleyen kullanıcıları etkiler.

Kodunuzu yeni iskeleyi DateOnly ve TimeOnly türleri kullanacak şekilde değiştirerek bu değişikliğe tepki vermeniz önerilir. Ancak bu mümkün değilse, yapı iskelesi şablonlarını düzenleyerek önceki eşlemeye dönebilirsiniz. Bunu yapmak için şablonları bu sayfada açıklandığı gibi ayarlayın. Ardından dosyayı düzenleyin EntityType.t4 , varlık özelliklerinin nerede oluşturulduğunu bulun (araması property.ClrTypeyapın) ve kodu aşağıdaki şekilde değiştirin:

        var clrType = property.GetColumnType() switch
        {
            "date" when property.ClrType == typeof(DateOnly) => typeof(DateTime),
            "date" when property.ClrType == typeof(DateOnly?) => typeof(DateTime?),
            "time" when property.ClrType == typeof(TimeOnly) => typeof(TimeSpan),
            "time" when property.ClrType == typeof(TimeOnly?) => typeof(TimeSpan?),
            _ => property.ClrType
        };

        usings.AddRange(code.GetRequiredUsings(clrType));

        var needsNullable = Options.UseNullableReferenceTypes && property.IsNullable && !clrType.IsValueType;
        var needsInitializer = Options.UseNullableReferenceTypes && !property.IsNullable && !clrType.IsValueType;
#>
    public <#= code.Reference(clrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#

Veritabanı tarafından oluşturulan değere sahip Boole sütunları artık null atanabilir olarak yapı iskelesi oluşturulmuyor

İzleme Sorunu #15070

Eski davranış

Daha önce, veritabanı varsayılan kısıtlaması olan null atanamayan bool sütunlar, boş değer atanabilir bool? özellikler olarak iskeleye alınıyordu.

Yeni davranış

EF Core 8.0'dan başlayarak, boş değer atanamayan bool sütunlar her zaman boş değer atanamayan özellikler olarak iskelelenir.

Neden?

Bir bool özelliğin değeri, clr varsayılanı olan falsedeğerine sahipse veritabanına gönderilmez. Veritabanı sütun için varsayılan değerine true sahipse, özelliğinin falsedeğeri olsa bile veritabanındaki değer olarak truebiter. Ancak EF8'de, bir özelliğin değeri olup olmadığını belirlemek için kullanılan sentinel değiştirilebilir. Bu, veritabanı tarafından oluşturulan değeri trueolan özellikler için bool otomatik olarak yapılır. Bu, artık özelliklerin null atanabilir olarak iskelesinin oluşturulmasının gerekli olmadığı anlamına gelir.

Risk Azaltıcı Etkenler

Bu değişiklik yalnızca veritabanlarını düzenli olarak bir EF kod modeline ("önce veritabanı" akışı) yeniden iskeleleyen kullanıcıları etkiler.

Kodunuzu null atanamaz bool özelliğini kullanacak şekilde değiştirerek bu değişikliğe tepki vermeniz önerilir. Ancak bu mümkün değilse, yapı iskelesi şablonlarını düzenleyerek önceki eşlemeye dönebilirsiniz. Bunu yapmak için şablonları bu sayfada açıklandığı gibi ayarlayın. Ardından dosyayı düzenleyin EntityType.t4 , varlık özelliklerinin nerede oluşturulduğunu bulun (araması property.ClrTypeyapın) ve kodu aşağıdaki şekilde değiştirin:

#>
        var propertyClrType = property.ClrType != typeof(bool)
                              || (property.GetDefaultValueSql() == null && property.GetDefaultValue() != null)
            ? property.ClrType
            : typeof(bool?);
#>
    public <#= code.Reference(propertyClrType) #><#= needsNullable ? "?" : "" #> <#= property.Name #> { get; set; }<#= needsInitializer ? " = null!;" : "" #>
<#
<#

Düşük etkili değişiklikler

SQLite Math yöntemleri artık SQL'e çevrilir

İzleme Sorunu #18843

Eski Davranış

Daha önce yalnızca üzerindeki Math Abs, Max, Min ve Round yöntemleri SQL'e çevrilmişti. Diğer tüm üyeler, sorgunun son Select ifadesinde görünürlerse istemcide değerlendirilir.

Yeni davranış

EF Core 8.0'da, karşılık gelen SQLite matematik işlevlerine sahip tüm Math yöntemler SQL'e çevrilir.

Bu matematik işlevleri varsayılan olarak sağladığımız yerel SQLite kitaplığında etkinleştirilmiştir (SQLitePCLRaw.bundle_e_sqlite3 NuGet paketine bağımlılığımız aracılığıyla). Bunlar, SQLitePCLRaw.bundle_e_sqlcipher tarafından sağlanan kitaplıkta da etkinleştirilmiştir. Bu kitaplıklardan birini kullanıyorsanız, uygulamanız bu değişiklikten etkilenmemelidir.

Ancak, diğer yollarla yerel SQLite kitaplığını içeren uygulamaların matematik işlevlerini etkinleştirmeme olasılığı vardır. Bu gibi durumlarda Math yöntemler SQL'e çevrilir ve yürütülürken böyle bir işlev hatasıyla karşılaşmaz.

Neden?

SQLite, 3.35.0 sürümüne yerleşik matematik işlevleri ekledi. Varsayılan olarak devre dışı bırakılmış olsalar da, EF Core SQLite sağlayıcımızda onlar için varsayılan çeviriler sağlamaya karar verdik.

Ayrıca Eric Sink ile SQLitePCLRaw projesi üzerinde işbirliği yaparak projenin bir parçası olarak sağlanan tüm yerel SQLite kitaplıklarında matematik işlevlerini etkinleştirdik.

Risk Azaltıcı Etkenler

Kesmeleri düzeltmenin en basit yolu, mümkün olduğunda derleme zamanı seçeneğini belirterek matematik işlevini etkinleştirmek SQLITE_ENABLE_MATH_FUNCTIONS için yerel SQLite kitaplığıdır.

Yerel kitaplığın derlenmesini denetlemezseniz, Microsoft.Data.Sqlite API'lerini kullanarak çalışma zamanında işlevleri kendiniz oluşturarak da kesmeleri düzeltebilirsiniz.

sqliteConnection
    .CreateFunction<double, double, double>(
        "pow",
        Math.Pow,
        isDeterministic: true);

Alternatif olarak, Select ifadesini ile AsEnumerableayrılmış iki bölüme bölerek istemci değerlendirmesini zorlayabilirsiniz.

// Before
var query = dbContext.Cylinders
    .Select(
        c => new
        {
            Id = c.Id
            // May throw "no such function: pow"
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

// After
var query = dbContext.Cylinders
    // Select the properties you'll need from the database
    .Select(
        c => new
        {
            c.Id,
            c.Radius,
            c.Height
        })
    // Switch to client-eval
    .AsEnumerable()
    // Select the final results
    .Select(
        c => new
        {
            Id = c.Id,
            Volume = Math.PI * Math.Pow(c.Radius, 2) * c.Height
        });

ITypeBase bazı API'lerde IEntityType'ın yerini alır

İzleme Sorunu #13947

Eski davranış

Daha önce tüm eşlenmiş yapısal türler varlık türleriydi.

Yeni davranış

EF8'de karmaşık türlerin kullanıma sunulmasıyla birlikte, daha önce kullanılmış IEntityType olan bazı API'ler artık api'lerin varlık veya karmaşık türlerle kullanılabilmesi için kullanılır ITypeBase . Buna aşağıdakiler dahildir:

  • IProperty.DeclaringEntityType artık kullanım dışıdır ve IProperty.DeclaringType bunun yerine kullanılmalıdır.
  • IEntityTypeIgnoredConvention artık kullanım dışıdır ve ITypeIgnoredConvention bunun yerine kullanılmalıdır.
  • IValueGeneratorSelector.Selectşimdi olabilir ancak olması gerekmeyen bir IEntityTypekabul ederITypeBase.

Neden?

EF8'de karmaşık türlerin kullanıma sunulmasıyla birlikte, bu API'ler veya IComplexTypeile IEntityType kullanılabilir.

Risk Azaltıcı Etkenler

Eski API'ler engellenir, ancak EF10'a kadar kaldırılmaz. Kod, en kısa sürede yeni API'leri kullanacak şekilde güncelleştirilmelidir.

ValueConverter ve ValueComparer ifadelerinin derlenen model için genel API'leri kullanması gerekir

İzleme Sorunu #24896

Eski davranış

ValueConverter Daha önce ve ValueComparer tanımları derlenen modele dahil edilmediğinden rastgele kod içerebiliyordu.

Yeni davranış

EF artık ve ValueComparer nesnelerinden ValueConverter ifadeleri ayıklar ve derlenen modele bu C# değerlerini ekler. Bu, bu ifadelerin yalnızca genel API kullanması gerektiği anlamına gelir.

Neden?

EF ekibi, gelecekte EF Core'un AOT ile kullanılmasını desteklemek için derlenen modele aşamalı olarak daha fazla yapı taşıyor.

Risk Azaltıcı Etkenler

Karşılaştırıcı tarafından kullanılan API'leri genel yapın. Örneğin, şu basit dönüştürücüye göz atın:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    private static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    private static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

Bu dönüştürücüsü EF8 ConvertToString ile derlenmiş bir modelde kullanmak için ve ConvertToBytes yöntemleri genele açık hale getirilmelidir. Örneğin:

public class MyValueConverter : ValueConverter<string, byte[]>
{
    public MyValueConverter()
        : base(v => ConvertToBytes(v), v => ConvertToString(v))
    {
    }

    public static string ConvertToString(byte[] bytes)
        => ""; // ... TODO: Conversion code

    public static byte[] ConvertToBytes(string chars)
        => Array.Empty<byte>(); // ... TODO: Conversion code
}

ExcludeFromMigrations artık TPC hiyerarşisindeki diğer tabloları dışlamıyor

İzleme Sorunu #30079

Eski davranış

Daha önce, TPC hiyerarşisindeki bir tabloda kullanmak ExcludeFromMigrations , hiyerarşideki diğer tabloları da dışlardı.

Yeni davranış

EF Core 8.0'dan başlayarak diğer ExcludeFromMigrations tabloları etkilemez.

Neden?

Eski davranış bir hataydı ve geçişlerin projeler arasında hiyerarşileri yönetmek için kullanılmasını engelledi.

Risk Azaltıcı Etkenler

Dışlanması gereken diğer tüm tablolarda açıkça kullanın ExcludeFromMigrations .

Gölge olmayan tamsayı anahtarları Cosmos belgelerinde kalıcı hale getiriliyor

İzleme Sorunu #31664

Eski davranış

Daha önce, sentezlenmiş anahtar özelliği ölçütleri ile eşleşen gölge olmayan tamsayı özellikleri JSON belgesinde kalıcı hale getirilmeyecek, ancak çıkışta yeniden sentezlenmişti.

Yeni davranış

EF Core 8.0'dan itibaren bu özellikler kalıcı hale getiriliyor.

Neden?

Eski davranış bir hataydı ve sentezlenen anahtar ölçütleriyle eşleşen özelliklerin Cosmos'ta kalıcı olmasını engelledi.

Risk Azaltıcı Etkenler

Değeri kalıcı olmaması gerekiyorsa özelliği modelin dışında tutun. Ayrıca, AppContext anahtarını olarak ayarlayarak Microsoft.EntityFrameworkCore.Issue31664 bu davranışı tamamen devre dışı bırakabilirsiniz. Daha fazla ayrıntı için bkz. Kitaplık tüketicileri için AppContext.true

AppContext.SetSwitch("Microsoft.EntityFrameworkCore.Issue31664", isEnabled: true);

İlişkisel model derlenmiş modelde oluşturulur

İzleme Sorunu #24896

Eski davranış

Daha önce ilişkisel model, derlenmiş bir model kullanılırken bile çalışma zamanında hesaplandı.

Yeni davranış

EF Core 8.0'dan başlayarak ilişkisel model, oluşturulan derlenmiş modelin bir parçasıdır. Ancak, özellikle büyük modeller için oluşturulan dosya derlenemiyor olabilir.

Neden?

Bu, başlangıç süresini daha da geliştirmek için yapıldı.

Risk Azaltıcı Etkenler

Oluşturulan *ModelBuilder.cs dosyayı düzenleyin ve yönteminin CreateRelationalModel()yanı sıra satırını AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); da kaldırın.

yapı iskelesi farklı gezinti adları oluşturabilir

İzleme Sorunu #27832

Eski davranış

Daha önce var olan bir veritabanından ve DbContext varlık türlerinin iskelesini oluştururken, ilişkiler için gezinti adları bazen birden çok yabancı anahtar sütun adının ortak ön ekinden türetilirdi.

Yeni davranış

EF Core 8.0'dan başlayarak, bileşik yabancı anahtardan sütun adlarının ortak ön ekleri artık gezinti adları oluşturmak için kullanılmaz.

Neden?

Bu, bazen gibi SStudent_çok kötü adlar oluşturan, hatta yalnızca _gibi belirsiz bir adlandırma kuralıdır. Bu kural olmadan, garip adlar artık oluşturulmaz ve gezintiler için adlandırma kuralları da daha basit hale getirildiğinden, hangi adların oluşturulacağını anlamak ve tahmin etmek daha kolay hale gelir.

Risk Azaltıcı Etkenler

EF Core Power Tools'un gezintileri eski yöntemle oluşturmaya devam etme seçeneği vardır. Alternatif olarak, oluşturulan kod T4 şablonları kullanılarak tamamen özelleştirilebilir. Bu, yapı iskelesi ilişkilerinin yabancı anahtar özelliklerini örnek almak ve ihtiyacınız olan gezinti adlarını oluşturmak için kodunuza uygun olan kuralı kullanmak için kullanılabilir.

Ayrımcıların artık uzunluk üstleri

İzleme Sorunu #10691

Eski davranış

Daha önce, TPH devralma eşlemesi için oluşturulan ayrımcı sütunlar SQL Server/Azure SQL'de veya diğer veritabanlarında eşdeğer ilişkisiz dize türünde olarak nvarchar(max) yapılandırıldı.

Yeni davranış

EF Core 8.0'dan başlayarak, ayırıcı sütunlar bilinen tüm ayrımcı değerleri kapsayan maksimum uzunlukla oluşturulur. EF, bu değişikliği yapmak için bir geçiş oluşturur. Ancak, ayrımcı sütun bir şekilde kısıtlanmışsa (örneğin, bir dizinin parçası olarak) Migrations AlterColumn tarafından oluşturulan başarısız olabilir.

Neden?

nvarchar(max) tüm olası değerlerin uzunlukları bilindiğinde sütunlar verimsiz ve gereksizdir.

Risk Azaltıcı Etkenler

Sütun boyutu açıkça ilişkisiz yapılabilir:

modelBuilder.Entity<Foo>()
    .Property<string>("Discriminator")
    .HasMaxLength(-1);

SQL Server anahtar değerleri büyük/küçük harfe duyarsız olarak karşılaştırılır

İzleme Sorunu #27526

Eski davranış

Daha önce SQL Server/Azure SQL veritabanı sağlayıcılarıyla dize anahtarları olan varlıkları izlerken, anahtar değerleri varsayılan .NET büyük/küçük harfe duyarlı sıralı karşılaştırıcı kullanılarak karşılaştırıldı.

Yeni davranış

EF Core 8.0'dan başlayarak, SQL Server/Azure SQL dize anahtarı değerleri varsayılan .NET büyük/küçük harfe duyarlı olmayan sıralı karşılaştırıcı kullanılarak karşılaştırılır.

Neden?

Varsayılan olarak, SQL Server eşleşmeler için yabancı anahtar değerlerini asıl anahtar değerleriyle karşılaştırırken büyük/küçük harfe duyarsız karşılaştırmalar kullanır. Bu, EF büyük/küçük harfe duyarlı karşılaştırmalar kullandığında, gerektiğinde yabancı anahtarı bir asıl anahtara bağlamayabileceği anlamına gelir.

Risk Azaltıcı Etkenler

Büyük/küçük harfe duyarlı karşılaştırmalar, özel ValueComparerayarlanarak kullanılabilir. Örnek:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var comparer = new ValueComparer<string>(
        (l, r) => string.Equals(l, r, StringComparison.Ordinal),
        v => v.GetHashCode(),
        v => v);

    modelBuilder.Entity<Blog>()
        .Property(e => e.Id)
        .Metadata.SetValueComparer(comparer);

    modelBuilder.Entity<Post>(
        b =>
        {
            b.Property(e => e.Id).Metadata.SetValueComparer(comparer);
            b.Property(e => e.BlogId).Metadata.SetValueComparer(comparer);
        });
}