Aracılığıyla paylaş


EF Core 6.0'daki Yenilikler

EF Core 6.0 NuGet'e gönderildi. Bu sayfa, bu sürümde tanıtılan ilginç değişikliklere genel bir bakış içerir.

Bahşiş

GitHub'dan örnek kodu indirerek aşağıda gösterilen örneklerde komutunu çalıştırabilir ve hata ayıklayabilirsiniz.

SQL Server zamana bağlı tabloları

GitHub Sorunu: #4693.

SQL Server zamana bağlı tabloları , veriler güncelleştirildikten veya silindikten sonra bile bir tabloda depolanan tüm verileri otomatik olarak izler. Bu, ana tabloda her değişiklik yapıldığında zaman damgasına sahip geçmiş verilerin depolandığı paralel bir "geçmiş tablosu" oluşturularak elde edilir. Bu, geçmiş verilerin denetleme veya geri yükleme gibi yanlışlıkla mutasyon veya silme sonrasında kurtarılması gibi sorgulanabilmesini sağlar.

EF Core artık şu desteği destekliyor:

  • Geçişler kullanılarak zamana bağlı tablolar oluşturma
  • Geçişleri kullanarak mevcut tabloların zamansal tablolara dönüştürülmesi
  • Geçmiş verileri sorgulama
  • Geçmişteki bir noktadan verileri geri yükleme

Zamansal tablo yapılandırma

Model oluşturucu, bir tabloyu zamansal olarak yapılandırmak için kullanılabilir. Örnek:

modelBuilder
    .Entity<Employee>()
    .ToTable("Employees", b => b.IsTemporal());

Veritabanını oluşturmak için EF Core kullanılırken, yeni tablo zaman damgaları ve geçmiş tablosu için SQL Server varsayılanlarıyla bir zamansal tablo olarak yapılandırılır. Örneğin, bir Employee varlık türü düşünün:

public class Employee
{
    public Guid EmployeeId { get; set; }
    public string Name { get; set; }
    public string Position { get; set; }
    public string Department { get; set; }
    public string Address { get; set; }
    public decimal AnnualSalary { get; set; }
}

Oluşturulan zamana bağlı tablo şöyle görünür:

DECLARE @historyTableSchema sysname = SCHEMA_NAME()
EXEC(N'CREATE TABLE [Employees] (
    [EmployeeId] uniqueidentifier NOT NULL,
    [Name] nvarchar(100) NULL,
    [Position] nvarchar(100) NULL,
    [Department] nvarchar(100) NULL,
    [Address] nvarchar(1024) NULL,
    [AnnualSalary] decimal(10,2) NOT NULL,
    [PeriodEnd] datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
    [PeriodStart] datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
    CONSTRAINT [PK_Employees] PRIMARY KEY ([EmployeeId]),
    PERIOD FOR SYSTEM_TIME([PeriodStart], [PeriodEnd])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[EmployeeHistory]))');

SQL Server'ın ve PeriodStartadlı PeriodEnd iki gizli datetime2 sütun oluşturduğuna dikkat edin. Bu "nokta sütunları", satırdaki verilerin bulunduğu zaman aralığını temsil eder. Bu sütunlar EF Core modelindeki gölge özelliklere eşlenir ve daha sonra gösterildiği gibi sorgularda kullanılmasına olanak sağlar.

Önemli

Bu sütunlardaki saatler her zaman SQL Server tarafından oluşturulan UTC saatidir. UTC saatleri, aşağıda gösterilen sorgular gibi zamana bağlı tabloları içeren tüm işlemler için kullanılır.

Ayrıca adlı EmployeeHistory ilişkili bir geçmiş tablosunun otomatik olarak oluşturulduğuna da dikkat edin. Dönem sütunlarının ve geçmiş tablosunun adları, model oluşturucusunun ek yapılandırmasıyla değiştirilebilir. Örnek:

modelBuilder
    .Entity<Employee>()
    .ToTable(
        "Employees",
        b => b.IsTemporal(
            b =>
            {
                b.HasPeriodStart("ValidFrom");
                b.HasPeriodEnd("ValidTo");
                b.UseHistoryTable("EmployeeHistoricalData");
            }));

Bu, SQL Server tarafından oluşturulan tabloya yansıtılır:

DECLARE @historyTableSchema sysname = SCHEMA_NAME()
EXEC(N'CREATE TABLE [Employees] (
    [EmployeeId] uniqueidentifier NOT NULL,
    [Name] nvarchar(100) NULL,
    [Position] nvarchar(100) NULL,
    [Department] nvarchar(100) NULL,
    [Address] nvarchar(1024) NULL,
    [AnnualSalary] decimal(10,2) NOT NULL,
    [ValidFrom] datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
    [ValidTo] datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
    CONSTRAINT [PK_Employees] PRIMARY KEY ([EmployeeId]),
    PERIOD FOR SYSTEM_TIME([ValidFrom], [ValidTo])
) WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = [' + @historyTableSchema + N'].[EmployeeHistoricalData]))');

Zamana bağlı tabloları kullanma

Çoğu zaman, zamansal tablolar diğer tablolarda olduğu gibi kullanılır. Başka bir ifadeyle, dönem sütunları ve geçmiş verileri SQL Server tarafından saydam bir şekilde işlenir ve böylece uygulama bunları yoksayabilir. Örneğin, yeni varlıklar veritabanına normal şekilde kaydedilebilir:

context.AddRange(
    new Employee
    {
        Name = "Pinky Pie",
        Address = "Sugarcube Corner, Ponyville, Equestria",
        Department = "DevDiv",
        Position = "Party Organizer",
        AnnualSalary = 100.0m
    },
    new Employee
    {
        Name = "Rainbow Dash",
        Address = "Cloudominium, Ponyville, Equestria",
        Department = "DevDiv",
        Position = "Ponyville weather patrol",
        AnnualSalary = 900.0m
    },
    new Employee
    {
        Name = "Fluttershy",
        Address = "Everfree Forest, Equestria",
        Department = "DevDiv",
        Position = "Animal caretaker",
        AnnualSalary = 30.0m
    });

context.SaveChanges();

Bu veriler daha sonra normal şekilde sorgulanabilir, güncelleştirilebilir ve silinebilir. Örnek:

var employee = context.Employees.Single(e => e.Name == "Rainbow Dash");
context.Remove(employee);
context.SaveChanges();

Ayrıca, normal bir izleme sorgusundan sonra, geçerli verilerin dönem sütunlarından değerlere izlenen varlıklardan erişilebilir. Örnek:

var employees = context.Employees.ToList();
foreach (var employee in employees)
{
    var employeeEntry = context.Entry(employee);
    var validFrom = employeeEntry.Property<DateTime>("ValidFrom").CurrentValue;
    var validTo = employeeEntry.Property<DateTime>("ValidTo").CurrentValue;

    Console.WriteLine($"  Employee {employee.Name} valid from {validFrom} to {validTo}");
}

Bu yazdırılır:

Starting data:
  Employee Pinky Pie valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM
  Employee Rainbow Dash valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM
  Employee Fluttershy valid from 8/26/2021 4:38:58 PM to 12/31/9999 11:59:59 PM

Sütunun ValidTo (varsayılan olarak olarak olarak adlandırılır PeriodEnd) maksimum değeri içerdiğine datetime2 dikkat edin. Tablodaki geçerli satırlar için her zaman böyledir. ValidFrom Sütunlar (varsayılan olarak olarak olarak adlandırılırPeriodStart) satırın eklendiği UTC saatini içerir.

Geçmiş verileri sorgulama

EF Core, birkaç yeni sorgu işleci aracılığıyla geçmiş verileri içeren sorguları destekler:

  • TemporalAsOf: Belirtilen UTC saatinde etkin olan (geçerli) satırları döndürür. Bu, belirli bir birincil anahtar için geçerli tablo veya geçmiş tablosundan tek bir satırdır.
  • TemporalAll: Geçmiş verilerdeki tüm satırları döndürür. Bu genellikle belirli bir birincil anahtar için geçmiş tablosundan ve/veya geçerli tablodan birçok satırdır.
  • TemporalFromTo: verilen iki UTC saat arasında etkin olan tüm satırları döndürür. Bu, geçmiş tablosundan ve/veya belirli bir birincil anahtar için geçerli tablodan çok sayıda satır olabilir.
  • TemporalBetween: üst sınırda etkin hale gelen satırların dahil olması dışında ile aynıdır TemporalFromTo.
  • TemporalContainedIn: Etkin olmaya başlayan ve verilen iki UTC saat arasında etkin olmaya son veren tüm satırları döndürür. Bu, geçmiş tablosundan ve/veya belirli bir birincil anahtar için geçerli tablodan çok sayıda satır olabilir.

Örneğin, verilerimizde bazı güncelleştirmeler ve silmeler yaptıktan sonra geçmiş verileri görmek için kullanarak TemporalAll bir sorgu çalıştırabiliriz:

var history = context
    .Employees
    .TemporalAll()
    .Where(e => e.Name == "Rainbow Dash")
    .OrderBy(e => EF.Property<DateTime>(e, "ValidFrom"))
    .Select(
        e => new
        {
            Employee = e,
            ValidFrom = EF.Property<DateTime>(e, "ValidFrom"),
            ValidTo = EF.Property<DateTime>(e, "ValidTo")
        })
    .ToList();

foreach (var pointInTime in history)
{
    Console.WriteLine(
        $"  Employee {pointInTime.Employee.Name} was '{pointInTime.Employee.Position}' from {pointInTime.ValidFrom} to {pointInTime.ValidTo}");
}

EF'nin nasıl olduğunu fark edin. Özellik yöntemi , dönem sütunlarından değerlere erişmek için kullanılabilir. Bu, yan tümcesinde OrderBy verileri sıralamak için kullanılır ve sonra bu değerleri döndürülen verilere eklemek için bir projeksiyonda kullanılır.

Bu sorgu aşağıdaki verileri geri getirir:

Historical data for Rainbow Dash:
  Employee Rainbow Dash was 'Ponyville weather patrol' from 8/26/2021 4:38:58 PM to 8/26/2021 4:40:29 PM
  Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
  Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM
  Employee Rainbow Dash was 'Wonderbolt' from 8/26/2021 4:43:29 PM to 8/26/2021 4:44:59 PM

Döndürülen son satırın 26.08.2021 16:44:59'da etkin olmayı durdurduğuna dikkat edin. Bunun nedeni Rainbow Dash satırının o sırada ana tablodan silinmesidir. Bu verilerin nasıl geri yüklenebileceğini daha sonra göreceğiz.

Benzer sorgular , veya TemporalBetweenTemporalContainedInkullanılarak TemporalFromToyazılabilir. Örnek:

var history = context
    .Employees
    .TemporalBetween(timeStamp2, timeStamp3)
    .Where(e => e.Name == "Rainbow Dash")
    .OrderBy(e => EF.Property<DateTime>(e, "ValidFrom"))
    .Select(
        e => new
        {
            Employee = e,
            ValidFrom = EF.Property<DateTime>(e, "ValidFrom"),
            ValidTo = EF.Property<DateTime>(e, "ValidTo")
        })
    .ToList();

Bu sorgu aşağıdaki satırları döndürür:

Historical data for Rainbow Dash between 8/26/2021 4:41:14 PM and 8/26/2021 4:42:44 PM:
  Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
  Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM

Geçmiş verileri geri yükleme

Yukarıda belirtildiği gibi, Rainbow Dash tablodan Employees silindi. Bu açıkça bir hataydı, bu nedenle belirli bir noktaya geri dönelim ve o zamandan itibaren eksik satırı geri yükleyelim.

var employee = context
    .Employees
    .TemporalAsOf(timeStamp2)
    .Single(e => e.Name == "Rainbow Dash");

context.Add(employee);
context.SaveChanges();

Bu sorgu, belirtilen UTC saatinde olduğu gibi Rainbow Dash için tek bir satır döndürür. Zamana bağlı işleçleri kullanan tüm sorgular varsayılan olarak izlenmez, bu nedenle burada döndürülen varlık izlenmez. Bu, şu anda ana tabloda mevcut olmadığından mantıklıdır. Varlığı ana tabloya yeniden eklemek için bunu olarak Added işaretlememiz ve çağrısı SaveChangesyapmamız yeterlidir.

Rainbow Dash satırını yeniden ekledikten sonra geçmiş verileri sorgulamak satırın verilen UTC saatinde olduğu gibi geri yüklendiğini gösterir:

Historical data for Rainbow Dash:
  Employee Rainbow Dash was 'Ponyville weather patrol' from 8/26/2021 4:38:58 PM to 8/26/2021 4:40:29 PM
  Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:40:29 PM to 8/26/2021 4:41:59 PM
  Employee Rainbow Dash was 'Wonderbolt Reservist' from 8/26/2021 4:41:59 PM to 8/26/2021 4:43:29 PM
  Employee Rainbow Dash was 'Wonderbolt' from 8/26/2021 4:43:29 PM to 8/26/2021 4:44:59 PM
  Employee Rainbow Dash was 'Wonderbolt Trainee' from 8/26/2021 4:44:59 PM to 12/31/9999 11:59:59 PM

Geçiş Paketleri

GitHub Sorunu: #19693.

EF Core geçişleri, EF modelinde yapılan değişikliklere göre veritabanı şeması güncelleştirmeleri oluşturmak için kullanılır. Bu şema güncelleştirmeleri genellikle sürekli tümleştirme/sürekli dağıtım (C.I./C.D.) sisteminin bir parçası olarak uygulama dağıtım zamanında uygulanmalıdır.

EF Core artık şu şema güncelleştirmelerini uygulamak için yeni bir yol içeriyor: geçiş paketleri. Geçiş paketi, geçişleri ve bu geçişleri veritabanına uygulamak için gereken kodu içeren küçük bir yürütülebilir dosyadır.

Geçiş paketleri, komut satırı aracı kullanılarak dotnet ef oluşturulur. Devam etmeden önce aracın en son sürümünü yüklediğinizden emin olun.

Bir paketin dahil edilmesi için geçişler gerekir. Bunlar, geçiş belgelerinde açıklandığı gibi kullanılarak dotnet ef migrations add oluşturulur. Geçişleri dağıtmaya hazır olduktan sonra kullanarak dotnet ef migrations bundlebir paket oluşturun. Örnek:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

Çıkış, hedef işletim sisteminiz için uygun bir yürütülebilir dosyadır. Benim durumumda bu Windows x64 olduğundan, yerel klasörüme bir efbundle.exe bırakma işlemi alıyorum. Bu yürütülebilir dosyanın çalıştırılması, içindeki geçişleri uygular:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903083845_MyMigration'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Geçişler veritabanına yalnızca henüz uygulanmamışsa uygulanır. Örneğin, geçerli olacak yeni geçişler olmadığından aynı paketi yeniden çalıştırmak hiçbir şey yapmaz:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
No migrations were applied. The database is already up to date.
Done.
PS C:\local\AllTogetherNow\SixOh>

Ancak modelde değişiklik yapılırsa ve ile dotnet ef migrations adddaha fazla geçiş oluşturulursa, bunlar uygulanmaya hazır yeni bir yürütülebilir dosyaya paketlenebilir. Örnek:

PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add SecondMigration
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations add Number3
Build started...
Build succeeded.
Done. To undo this action, use 'ef migrations remove'
PS C:\local\AllTogetherNow\SixOh> dotnet ef migrations bundle --force
Build started...
Build succeeded.
Building bundle...
Done. Migrations Bundle: C:\local\AllTogetherNow\SixOh\efbundle.exe
PS C:\local\AllTogetherNow\SixOh>

Seçeneğin --force mevcut paketin üzerine yeni bir paket yazmak için kullanılabildiğini görebilirsiniz.

Bu yeni paketin yürütülmesi veritabanına şu iki yeni geçişi uygular:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Paket varsayılan olarak uygulamanızın yapılandırmasından bağlantı dizesi veritabanını kullanır. Ancak, bağlantı dizesi komut satırına geçirilerek farklı bir veritabanı geçirilebilir. Örnek:

PS C:\local\AllTogetherNow\SixOh> .\efbundle.exe --connection "Data Source=(LocalDb)\MSSQLLocalDB;Database=SixOhProduction"
Applying migration '20210903083845_MyMigration'.
Applying migration '20210903084526_SecondMigration'.
Applying migration '20210903084538_Number3'.
Done.
PS C:\local\AllTogetherNow\SixOh>

Bu kez üç geçişin de uygulandığına dikkat edin çünkü bunların hiçbiri henüz üretim veritabanına uygulanmamıştır.

Diğer seçenekler komut satırına geçirilebilir. Bazı yaygın seçenekler şunlardır:

  • --output oluşturulacak yürütülebilir dosyanın yolunu belirtmek için.
  • --context proje birden çok bağlam türü içerdiğinde kullanılacak DbContext türünü belirtmek için.
  • --project öğesini seçin. Varsayılan olarak geçerli çalışma dizinine geçer.
  • --startup-project öğesini seçin. Varsayılan olarak geçerli çalışma dizinine geçer.
  • --no-build komutunu çalıştırmadan önce projenin derlenmesini önlemek için. Bu yalnızca projenin güncel olduğu biliniyorsa kullanılmalıdır.
  • --verbose komutunun ne yaptığına ilişkin ayrıntılı bilgileri görmek için. Hata raporlarına bilgi eklerken bu seçeneği kullanın.

Kullanılabilir tüm seçenekleri görmek için kullanın dotnet ef migrations bundle --help .

Varsayılan olarak her geçişin kendi işlemine uygulandığını unutmayın. Bu alanda gelecekteki olası geliştirmeler hakkında bilgi edinmek için GitHub sorunu #22616'ya bakın.

Kural öncesi model yapılandırması

GitHub Sorunu: #12229.

EF Core'un önceki sürümleri, belirli bir türün her özelliği için eşlemenin, bu eşleme varsayılandan farklı olduğunda açıkça yapılandırılmasını gerektirir. Bu, en fazla dize uzunluğu ve ondalık duyarlık gibi "modeller" ile özellik türü için değer dönüştürmeyi içerir.

Bu, aşağıdakilerden birini gerekli kıldı:

  • Her özellik için model oluşturucu yapılandırması
  • Her özellik üzerinde bir eşleme özniteliği
  • Tüm varlık türlerinin tüm özellikleri üzerinde açık yineleme ve modeli oluştururken alt düzey meta veri API'lerinin kullanımı.

Varlık türlerinin ve eşlenen özelliklerin listesi bu yineleme gerçekleştiğinde nihai olmayabileceği için açık yinelemenin hataya açık olduğunu ve güçlü bir şekilde yapılması zor olduğunu unutmayın.

EF Core 6.0, bu eşleme yapılandırmasının belirli bir tür için bir kez belirtilmesine izin verir. Ardından modeldeki bu türdeki tüm özelliklere uygulanır. Buna "kural öncesi model yapılandırması" denir, çünkü modelin daha sonra model oluşturma kuralları tarafından kullanılan yönlerini yapılandırılır. Bu tür bir yapılandırma, üzerinde DbContextgeçersiz kılınarak ConfigureConventions uygulanır:

public class SomeDbContext : DbContext
{
    protected override void ConfigureConventions(
        ModelConfigurationBuilder configurationBuilder)
    {
        // Pre-convention model configuration goes here
    }
}

Örneğin, aşağıdaki varlık türlerini göz önünde bulundurun:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsActive { get; set; }
    public Money AccountValue { get; set; }

    public Session CurrentSession { get; set; }

    public ICollection<Order> Orders { get; } = new List<Order>();
}

public class Order
{
    public int Id { get; set; }
    public string SpecialInstructions { get; set; }
    public DateTime OrderDate { get; set; }
    public bool IsComplete { get; set; }
    public Money Price { get; set; }
    public Money? Discount { get; set; }

    public Customer Customer { get; set; }
}

Tüm dize özellikleri ANSI (Unicode yerine) olacak şekilde yapılandırılabilir ve uzunluğu en fazla 1024 olabilir:

configurationBuilder
    .Properties<string>()
    .AreUnicode(false)
    .HaveMaxLength(1024);

Tüm DateTime özellikleri, DateTimes'dan longs'a varsayılan dönüştürme kullanılarak veritabanındaki 64 bit tamsayılara dönüştürülebilir:

configurationBuilder
    .Properties<DateTime>()
    .HaveConversion<long>();

Tüm bool özellikleri tamsayılara 0 dönüştürülebilir veya 1 yerleşik değer dönüştürücülerinden biri kullanılarak:

configurationBuilder
    .Properties<bool>()
    .HaveConversion<BoolToZeroOneConverter<int>>();

Varlığın geçici bir özelliği olduğu ve kalıcı olmaması gerektiği varsayıldığında Session , modeldeki her yerde yoksayılabilir:

configurationBuilder
    .IgnoreAny<Session>();

Kural öncesi model yapılandırması, değer nesneleriyle çalışırken çok yararlıdır. Örneğin, yukarıdaki modeldeki tür Money salt okunur yapı ile temsil edilir:

public readonly struct Money
{
    [JsonConstructor]
    public Money(decimal amount, Currency currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public override string ToString()
        => (Currency == Currency.UsDollars ? "$" : "£") + Amount;

    public decimal Amount { get; }
    public Currency Currency { get; }
}

public enum Currency
{
    UsDollars,
    PoundsSterling
}

Bu işlem daha sonra özel bir değer dönüştürücüsü kullanılarak JSON'a ve JSON'dan seri hale getirilir:

public class MoneyConverter : ValueConverter<Money, string>
{
    public MoneyConverter()
        : base(
            v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
            v => JsonSerializer.Deserialize<Money>(v, (JsonSerializerOptions)null))
    {
    }
}

Bu değer dönüştürücü, Money'nin tüm kullanımları için bir kez yapılandırılabilir:

configurationBuilder
    .Properties<Money>()
    .HaveConversion<MoneyConverter>()
    .HaveMaxLength(64);

Serileştirilmiş JSON'un depolandığı dize sütunu için ek modeller de belirtilebildiğine dikkat edin. Bu durumda sütun en fazla 64 uzunlukla sınırlıdır.

Geçişler kullanılarak SQL Server için oluşturulan tablolar, yapılandırmanın tüm eşlenen sütunlara nasıl uygulandığını gösterir:

CREATE TABLE [Customers] (
    [Id] int NOT NULL IDENTITY,
    [Name] varchar(1024) NULL,
    [IsActive] int NOT NULL,
    [AccountValue] nvarchar(64) NOT NULL,
    CONSTRAINT [PK_Customers] PRIMARY KEY ([Id])
);
CREATE TABLE [Order] (
    [Id] int NOT NULL IDENTITY,
    [SpecialInstructions] varchar(1024) NULL,
    [OrderDate] bigint NOT NULL,
    [IsComplete] int NOT NULL,
    [Price] nvarchar(64) NOT NULL,
    [Discount] nvarchar(64) NULL,
    [CustomerId] int NULL,
    CONSTRAINT [PK_Order] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Order_Customers_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [Customers] ([Id])
);

Belirli bir tür için varsayılan tür eşlemesi de belirtebilirsiniz. Örnek:

configurationBuilder
    .DefaultTypeMapping<string>()
    .IsUnicode(false);

Bu nadiren gereklidir, ancak bir tür sorguda modelin eşlenen özellikleriyle bağıntısız bir şekilde kullanılıyorsa yararlı olabilir.

Dekont

Daha fazla tartışma ve kural öncesi model yapılandırması örnekleri için bkz . Entity Framework Core 6.0 Önizleme 6: Kuralları Yapılandırma .

Derlenmiş modeller

GitHub Sorunu: #1906.

Derlenmiş modeller, büyük modellere sahip uygulamalar için EF Core başlangıç süresini iyileştirebilir. Büyük bir model genellikle 100'lerden 1000'lere kadar varlık türü ve ilişki anlamına gelir.

Başlangıç zamanı, dbContext türü uygulamada ilk kez kullanıldığında DbContext üzerinde ilk işlemi gerçekleştirme zamanı anlamına gelir. DbContext örneği oluşturmanın EF modelinin başlatılmasına neden olmadığını unutmayın. Bunun yerine, modelin başlatılmasına neden olan tipik ilk işlemler ilk sorguyu çağırmak DbContext.Add veya yürütmektir.

Derlenen modeller komut satırı aracı kullanılarak dotnet ef oluşturulur. Devam etmeden önce aracın en son sürümünü yüklediğinizden emin olun.

Derlenmiş modeli oluşturmak için yeni dbcontext optimize bir komut kullanılır. Örnek:

dotnet ef dbcontext optimize

--output-dir ve --namespace seçenekleri, derlenen modelin oluşturulacağı dizini ve ad alanını belirtmek için kullanılabilir. Örnek:

PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels> dotnet ef dbcontext optimize --output-dir MyCompiledModels --namespace MyCompiledModels
Build started...
Build succeeded.
Successfully generated a compiled model, to use it call 'options.UseModel(MyCompiledModels.BlogsContextModel.Instance)'. Run this command again when the model is modified.
PS C:\dotnet\efdocs\samples\core\Miscellaneous\CompiledModels>

Bu komutun çalıştırılmasından elde edilen çıkış, EF Core'un derlenmiş modeli kullanmasına neden olmak için DbContext yapılandırmanıza kopyalayıp yapıştıracak bir kod parçası içerir. Örnek:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder
        .UseModel(MyCompiledModels.BlogsContextModel.Instance)
        .UseSqlite(@"Data Source=test.db");

Derlenmiş model önyüklemesi

Genellikle oluşturulan önyükleme koduna bakmak gerekmez. Ancak bazen modeli veya yüklemesini özelleştirmek yararlı olabilir. Bootstrapping kodu şuna benzer:

[DbContext(typeof(BlogsContext))]
partial class BlogsContextModel : RuntimeModel
{
    private static BlogsContextModel _instance;
    public static IModel Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new BlogsContextModel();
                _instance.Initialize();
                _instance.Customize();
            }

            return _instance;
        }
    }

    partial void Initialize();

    partial void Customize();
}

Bu, modeli gerektiği gibi özelleştirmek için uygulanabilen kısmi yöntemlere sahip kısmi bir sınıftır.

Ayrıca, bazı çalışma zamanı yapılandırmasına bağlı olarak farklı modeller kullanabilen DbContext türleri için birden çok derlenmiş model oluşturulabilir. Bunlar, yukarıda gösterildiği gibi farklı klasörlere ve ad alanlarına yerleştirilmelidir. Daha sonra bağlantı dizesi gibi çalışma zamanı bilgileri incelenebilir ve gerektiğinde doğru model döndürülebilir. Örnek:

public static class RuntimeModelCache
{
    private static readonly ConcurrentDictionary<string, IModel> _runtimeModels
        = new();

    public static IModel GetOrCreateModel(string connectionString)
        => _runtimeModels.GetOrAdd(
            connectionString, cs =>
            {
                if (cs.Contains("X"))
                {
                    return BlogsContextModel1.Instance;
                }

                if (cs.Contains("Y"))
                {
                    return BlogsContextModel2.Instance;
                }

                throw new InvalidOperationException("No appropriate compiled model found.");
            });
}

Sınırlamalar

Derlenen modellerin bazı sınırlamaları vardır:

Bu sınırlamalar nedeniyle, derlenmiş modelleri yalnızca EF Core başlangıç süreniz çok yavaşsa kullanmanız gerekir. Küçük modelleri derlemek genellikle buna değmez.

Bu özelliklerden herhangi birinin desteklenmesi başarınız için kritik önem taşıyorsa lütfen yukarıda belirtilen uygun sorunlar için oy verin.

Karşılaştırmalar

Bahşiş

GitHub'dan örnek kodu indirerek büyük bir model derlemeyi ve bu model üzerinde bir karşılaştırma çalıştırmayı deneyebilirsiniz.

Yukarıda başvuruda bulunılan GitHub deposundaki model 449 varlık türü, 6390 özellik ve 720 ilişki içerir. Bu, orta derecede büyük bir modeldir. Ölçmek için BenchmarkDotNet kullanıldığında, makul düzeyde güçlü bir dizüstü bilgisayarda ilk sorgunun ortalama süresi 1,02 saniyedir. Derlenmiş modellerin kullanılması bunu aynı donanımda 117 milisaniyeye indiriyor. Model boyutu arttıkça bunun gibi 8x-10 kat iyileştirmeler görece sabit kalır.

Compiled model performance improvement

Dekont

EF Core başlangıç performansı ve derlenmiş modeller hakkında daha ayrıntılı bir tartışma için .NET Blogu'nda Entity Framework Core 6.0 Önizleme 5: Derlenmiş Modeller Duyuruları'na bakın.

TechEmpower Fortunes'ta geliştirilmiş performans

GitHub Sorunu: #23611.

EF Core 6.0 için sorgu performansında önemli geliştirmeler yaptık. Özellikle:

  • EF Core 6.0 performansı, endüstri standardı TechEmpower Fortunes kıyaslamasında 5,0 ile karşılaştırıldığında artık %70 daha hızlıdır.
    • Bu, karşılaştırma kodundaki iyileştirmeler, .NET çalışma zamanı vb. dahil olmak üzere tam yığın performans geliştirmesidir.
  • EF Core 6.0'ın kendisi, izlenmeyen sorguları %31 daha hızlı yürütür.
  • Sorgu yürütülürken yığın ayırmaları %43 azaltıldı.

Bu geliştirmelerden sonra TechEmpower Fortunes karşılaştırmasında popüler "micro-ORM" Dapper ve EF Core arasındaki boşluk %55'ten %5'in biraz altına indi.

Dekont

EF Core 6.0'daki sorgu performansı iyileştirmelerinin ayrıntılı bir tartışması için .NET Blogu'ndaki Entity Framework Core 6.0 Önizleme 4: Performance Edition Duyuruları'na bakın.

Azure Cosmos DB sağlayıcısı geliştirmeleri

EF Core 6.0, Azure Cosmos DB veritabanı sağlayıcısında birçok geliştirme içerir.

Bahşiş

GitHub'dan örnek kodu indirerek Cosmos'a özgü tüm örnekleri çalıştırabilir ve hatalarını ayıklayabilirsiniz.

Varsayılan olarak örtük sahiplik

GitHub Sorunu: #24803.

Azure Cosmos DB sağlayıcısı için model oluştururken EF Core 6.0 alt varlık türlerini varsayılan olarak üst varlıklarına ait olarak işaretler. Bu, Azure Cosmos DB modelindeki ve OwnsOne çağrılarının büyük bir OwnsMany kısmının gereksinimini ortadan kaldırır. Bu, üst tür için belgeye alt türleri eklemeyi kolaylaştırır. Bu, belge veritabanındaki üst ve alt öğeleri modellemenin genellikle uygun yoludur.

Örneğin, şu varlık türlerini göz önünde bulundurun:

public class Family
{
    [JsonPropertyName("id")]
    public string Id { get; set; }

    public string LastName { get; set; }
    public bool IsRegistered { get; set; }

    public Address Address { get; set; }

    public IList<Parent> Parents { get; } = new List<Parent>();
    public IList<Child> Children { get; } = new List<Child>();
}

public class Parent
{
    public string FamilyName { get; set; }
    public string FirstName { get; set; }
}

public class Child
{
    public string FamilyName { get; set; }
    public string FirstName { get; set; }
    public int Grade { get; set; }

    public string Gender { get; set; }

    public IList<Pet> Pets { get; } = new List<Pet>();
}

EF Core 5.0'da bu türler Azure Cosmos DB için aşağıdaki yapılandırmayla modellenmiş olabilir:

modelBuilder.Entity<Family>()
    .HasPartitionKey(e => e.LastName)
    .OwnsMany(f => f.Parents);

modelBuilder.Entity<Family>()
    .OwnsMany(f => f.Children)
    .OwnsMany(c => c.Pets);

modelBuilder.Entity<Family>()
    .OwnsOne(f => f.Address);

EF Core 6.0'da sahiplik örtük olduğundan model yapılandırması şu şekilde azaltılır:

modelBuilder.Entity<Family>().HasPartitionKey(e => e.LastName);

Sonuçta elde edilen Azure Cosmos DB belgelerinde ailenin ebeveynleri, çocukları, evcil hayvanları ve adresi aile belgesine eklenir. Örnek:

{
  "Id": "Wakefield.7",
  "LastName": "Wakefield",
  "Discriminator": "Family",
  "IsRegistered": true,
  "id": "Family|Wakefield.7",
  "Address": {
    "City": "NY",
    "County": "Manhattan",
    "State": "NY"
  },
  "Children": [
    {
      "FamilyName": "Merriam",
      "FirstName": "Jesse",
      "Gender": "female",
      "Grade": 8,
      "Pets": [
        {
          "GivenName": "Goofy"
        },
        {
          "GivenName": "Shadow"
        }
      ]
    },
    {
      "FamilyName": "Miller",
      "FirstName": "Lisa",
      "Gender": "female",
      "Grade": 1,
      "Pets": []
    }
  ],
  "Parents": [
    {
      "FamilyName": "Wakefield",
      "FirstName": "Robin"
    },
    {
      "FamilyName": "Miller",
      "FirstName": "Ben"
    }
  ],
  "_rid": "x918AKh6p20CAAAAAAAAAA==",
  "_self": "dbs/x918AA==/colls/x918AKh6p20=/docs/x918AKh6p20CAAAAAAAAAA==/",
  "_etag": "\"00000000-0000-0000-adee-87f30c8c01d7\"",
  "_attachments": "attachments/",
  "_ts": 1632121802
}

Dekont

Bu sahip olunan türleri daha fazla yapılandırmanız gerekiyorsa yapılandırmanın OwnsOne/OwnsMany kullanılması gerektiğini unutmayın.

Temel tür koleksiyonları

GitHub Sorunu: #14762.

EF Core 6.0, Azure Cosmos DB veritabanı sağlayıcısını kullanırken ilkel tür koleksiyonlarını yerel olarak eşler. Örneğin, şu varlık türünü göz önüne alın:

public class Book
{
    public Guid Id { get; set; }
    public string Title { get; set; }
    public IList<string> Quotes { get; set; }
    public IDictionary<string, string> Notes { get; set; }
}

Hem liste hem de sözlük normal şekilde doldurulabilir ve veritabanına eklenebilir:

using var context = new BooksContext();

var book = new Book
{
    Title = "How It Works: Incredible History",
    Quotes = new List<string>
    {
        "Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
        "Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
        "For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
    },
    Notes = new Dictionary<string, string>
    {
        { "121", "Fridges" },
        { "144", "Peter Higgs" },
        { "48", "Saint Mark's Basilica" },
        { "36", "The Terracotta Army" }
    }
};

context.Add(book);
await context.SaveChangesAsync();

Bu, aşağıdaki JSON belgesi ile sonuçlanır:

{
    "Id": "0b32283e-22a8-4103-bb4f-6052604868bd",
    "Discriminator": "Book",
    "Notes": {
        "36": "The Terracotta Army",
        "48": "Saint Mark's Basilica",
        "121": "Fridges",
        "144": "Peter Higgs"
    },
    "Quotes": [
        "Thomas (Tommy) Flowers was the British engineer behind the design of the Colossus computer.",
        "Invented originally for Guinness, plastic widgets are nitrogen-filled spheres.",
        "For 20 years after its introduction in 1979, the Walkman dominated the personal stereo market."
    ],
    "Title": "How It Works: Incredible History",
    "id": "Book|0b32283e-22a8-4103-bb4f-6052604868bd",
    "_rid": "t-E3AIxaencBAAAAAAAAAA==",
    "_self": "dbs/t-E3AA==/colls/t-E3AIxaenc=/docs/t-E3AIxaencBAAAAAAAAAA==/",
    "_etag": "\"00000000-0000-0000-9b50-fc769dc901d7\"",
    "_attachments": "attachments/",
    "_ts": 1630075016
}

Bu koleksiyonlar daha sonra normal şekilde yeniden güncelleştirilebilir:

book.Quotes.Add("Pressing the emergency button lowered the rods again.");
book.Notes["48"] = "Chiesa d'Oro";

await context.SaveChangesAsync();

Sınırlamalar:

  • Yalnızca dize anahtarlarına sahip sözlükler desteklenir
  • Temel koleksiyonların içeriğini sorgulama şu anda desteklenmez. Bu özellikler sizin için önemliyse #16926, #25700 ve #25701 için oy verin.

Yerleşik işlevlere çeviriler

GitHub Sorunu: #16143.

Azure Cosmos DB sağlayıcısı artık daha fazla Temel Sınıf Kitaplığı (BCL) yöntemini Azure Cosmos DB yerleşik işlevlerine çevirmektedir. Aşağıdaki tablolarda EF Core 6.0'da yeni olan çeviriler gösterilmektedir.

Dize çevirileri

BCL yöntemi Yerleşik işlev Notlar
String.Length LENGTH
String.ToLower LOWER
String.TrimStart LTRIM
String.TrimEnd RTRIM
String.Trim TRIM
String.ToUpper UPPER
String.Substring SUBSTRING
+ Işleç CONCAT
String.IndexOf INDEX_OF
String.Replace REPLACE
String.Equals STRINGEQUAL Yalnızca büyük/küçük harfe duyarlı olmayan çağrılar

, , LTRIM, RTRIM, TRIM, ve UPPERSUBSTRING çevirileri LOWER@Marusyk tarafından katkıda bulundu. Çok teşekkürler!

Örneğin:

var stringResults = await context.Triangles.Where(
        e => e.Name.Length > 4
             && e.Name.Trim().ToLower() != "obtuse"
             && e.Name.TrimStart().Substring(2, 2).Equals("uT", StringComparison.OrdinalIgnoreCase))
    .ToListAsync();

Şu ifadeye çevrilir:

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Triangle") AND (((LENGTH(c["Name"]) > 4) AND (LOWER(TRIM(c["Name"])) != "obtuse")) AND STRINGEQUALS(SUBSTRING(LTRIM(c["Name"]), 2, 2), "uT", true)))

Matematik çevirileri

BCL yöntemi Yerleşik işlev
Math.Abs veya MathF.Abs ABS
Math.Acos veya MathF.Acos ACOS
Math.Asin veya MathF.Asin ASIN
Math.Atan veya MathF.Atan ATAN
Math.Atan2 veya MathF.Atan2 ATN2
Math.Ceiling veya MathF.Ceiling CEILING
Math.Cos veya MathF.Cos COS
Math.Exp veya MathF.Exp EXP
Math.Floor veya MathF.Floor FLOOR
Math.Log veya MathF.Log LOG
Math.Log10 veya MathF.Log10 LOG10
Math.Pow veya MathF.Pow POWER
Math.Round veya MathF.Round ROUND
Math.Sign veya MathF.Sign SIGN
Math.Sin veya MathF.Sin SIN
Math.Sqrt veya MathF.Sqrt SQRT
Math.Tan veya MathF.Tan TAN
Math.Truncate veya MathF.Truncate TRUNC
DbFunctions.Random RAND

Bu çeviriler @Marusyk tarafından katkıda bulunmuştur. Çok teşekkürler!

Örneğin:

var hypotenuse = 42.42;
var mathResults = await context.Triangles.Where(
        e => (Math.Round(e.Angle1) == 90.0
              || Math.Round(e.Angle2) == 90.0)
             && (hypotenuse * Math.Sin(e.Angle1) > 30.0
                 || hypotenuse * Math.Cos(e.Angle2) > 30.0))
    .ToListAsync();

Şu ifadeye çevrilir:

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Triangle") AND (((ROUND(c["Angle1"]) = 90.0) OR (ROUND(c["Angle2"]) = 90.0)) AND (((@__hypotenuse_0 * SIN(c["Angle1"])) > 30.0) OR ((@__hypotenuse_0 * COS(c["Angle2"])) > 30.0))))

DateTime çevirileri

BCL yöntemi Yerleşik işlev
DateTime.UtcNow GetCurrentDateTime

Bu çeviriler @Marusyk tarafından katkıda bulunmuştur. Çok teşekkürler!

Örneğin:

var timeResults = await context.Triangles.Where(
        e => e.InsertedOn <= DateTime.UtcNow)
    .ToListAsync();

Şu ifadeye çevrilir:

SELECT c
FROM root c
WHERE ((c["Discriminator"] = "Triangle") AND (c["InsertedOn"] <= GetCurrentDateTime()))

FromSql ile ham SQL sorguları

GitHub Sorunu: #17311.

Bazen LINQ kullanmak yerine ham bir SQL sorgusu yürütmek gerekir. Bu, artık yöntemi kullanılarak Azure Cosmos DB sağlayıcısında desteklenmektedir FromSql . Bu, ilişkisel sağlayıcılarla her zaman olduğu gibi çalışır. Örnek:

var maxAngle = 60;
var results = await context.Triangles.FromSqlRaw(
        @"SELECT * FROM root c WHERE c[""Angle1""] <= {0} OR c[""Angle2""] <= {0}", maxAngle)
    .ToListAsync();

Şu şekilde yürütülür:

SELECT c
FROM (
    SELECT * FROM root c WHERE c["Angle1"] <= @p0 OR c["Angle2"] <= @p0
) c

Ayrı sorgular

GitHub Sorunu: #16144.

Kullanan Distinct basit sorgular artık çevrilmiştir. Örnek:

var distinctResults = await context.Triangles
    .Select(e => e.Angle1).OrderBy(e => e).Distinct()
    .ToListAsync();

Şu ifadeye çevrilir:

SELECT DISTINCT c["Angle1"]
FROM root c
WHERE (c["Discriminator"] = "Triangle")
ORDER BY c["Angle1"]

Tanılama

GitHub Sorunu: #17298.

Azure Cosmos DB sağlayıcısı artık veritabanından veri ekleme, sorgulama, güncelleştirme ve silme olayları da dahil olmak üzere daha fazla tanılama bilgisi günlüğe kaydeder. İstek birimleri (RU) uygun olduğunda bu olaylara dahil edilir.

Dekont

Günlükler burada kimlik değerlerinin gösterilmesi için kullanılır EnableSensitiveDataLogging() olarak gösterilir.

Azure Cosmos DB veritabanına öğe eklemek olayı oluşturur CosmosEventId.ExecutedCreateItem . Örneğin, bu kod:

var triangle = new Triangle
{
    Name = "Impossible",
    PartitionKey = "TrianglesPartition",
    Angle1 = 90,
    Angle2 = 90,
    InsertedOn = DateTime.UtcNow
};
context.Add(triangle);
await context.SaveChangesAsync();

Aşağıdaki tanılama olayını günlüğe kaydeder:

info: 8/30/2021 14:41:13.356 CosmosEventId.ExecutedCreateItem[30104] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed CreateItem (5 ms, 7.43 RU) ActivityId='417db46f-fcdd-49d9-a7f0-77210cd06f84', Container='Shapes', Id='Impossible', Partition='TrianglesPartition'

Sorgu kullanarak Azure Cosmos DB veritabanından öğe alınması olayı oluşturur ve ardından okunan öğeler için bir veya daha fazla CosmosEventId.ExecutedReadNext olay oluştururCosmosEventId.ExecutingSqlQuery. Örneğin, bu kod:

var equilateral = await context.Triangles.SingleAsync(e => e.Name == "Equilateral");

Aşağıdaki tanılama olaylarını günlüğe kaydeder:

info: 8/30/2021 14:41:13.475 CosmosEventId.ExecutingSqlQuery[30100] (Microsoft.EntityFrameworkCore.Database.Command)
      Executing SQL query for container 'Shapes' in partition '(null)' [Parameters=[]]
      SELECT c
      FROM root c
      WHERE ((c["Discriminator"] = "Triangle") AND (c["id"] = "Equilateral"))
      OFFSET 0 LIMIT 2
info: 8/30/2021 14:41:13.651 CosmosEventId.ExecutedReadNext[30102] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed ReadNext (169.6126 ms, 2.93 RU) ActivityId='4e465fae-3d49-4c1f-bd04-142bc5d0b0a1', Container='Shapes', Partition='(null)', Parameters=[]
      SELECT c
      FROM root c
      WHERE ((c["Discriminator"] = "Triangle") AND (c["id"] = "Equilateral"))
      OFFSET 0 LIMIT 2

Bir bölüm anahtarıyla kullanarak Find Azure Cosmos DB veritabanından tek bir öğe alınması ve CosmosEventId.ExecutedReadItem olaylarını CosmosEventId.ExecutingReadItem oluşturur. Örneğin, bu kod:

var isosceles = await context.Triangles.FindAsync("Isosceles", "TrianglesPartition");

Aşağıdaki tanılama olaylarını günlüğe kaydeder:

info: 8/30/2021 14:53:39.326 CosmosEventId.ExecutingReadItem[30101] (Microsoft.EntityFrameworkCore.Database.Command)
      Reading resource 'Isosceles' item from container 'Shapes' in partition 'TrianglesPartition'.
info: 8/30/2021 14:53:39.330 CosmosEventId.ExecutedReadItem[30103] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed ReadItem (1 ms, 1 RU) ActivityId='3c278643-4e7f-4bb2-9953-6055b5f1288f', Container='Shapes', Id='Isosceles', Partition='TrianglesPartition'

Güncelleştirilmiş bir öğenin Azure Cosmos DB veritabanına kaydedilmesi olayı oluşturur CosmosEventId.ExecutedReplaceItem . Örneğin, bu kod:

triangle.Angle2 = 89;
await context.SaveChangesAsync();

Aşağıdaki tanılama olayını günlüğe kaydeder:

info: 8/30/2021 14:53:39.343 CosmosEventId.ExecutedReplaceItem[30105] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed ReplaceItem (6 ms, 10.67 RU) ActivityId='1525b958-fea1-49e8-89f9-d429d0351fdb', Container='Shapes', Id='Impossible', Partition='TrianglesPartition'

Azure Cosmos DB veritabanından bir öğe silindiğinde olay oluşturur CosmosEventId.ExecutedDeleteItem . Örneğin, bu kod:

context.Remove(triangle);
await context.SaveChangesAsync();

Aşağıdaki tanılama olayını günlüğe kaydeder:

info: 8/30/2021 14:53:39.359 CosmosEventId.ExecutedDeleteItem[30106] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DeleteItem (6 ms, 7.43 RU) ActivityId='cbc54463-405b-48e7-8c32-2c6502a4138f', Container='Shapes', Id='Impossible', Partition='TrianglesPartition'

Aktarım hızını yapılandırma

GitHub Sorunu: #17301.

Azure Cosmos DB modeli artık el ile veya otomatik ölçeklendirme aktarım hızıyla yapılandırılabilir. Bu değerler veritabanında aktarım hızı sağlar. Örnek:

modelBuilder.HasManualThroughput(2000);
modelBuilder.HasAutoscaleThroughput(4000);

Ayrıca, tek tek varlık türleri ilgili kapsayıcı için aktarım hızı sağlamak üzere yapılandırılabilir. Örnek:

modelBuilder.Entity<Family>(
    entityTypeBuilder =>
    {
        entityTypeBuilder.HasManualThroughput(5000);
        entityTypeBuilder.HasAutoscaleThroughput(3000);
    });

Yaşam süresi yapılandırma

GitHub Sorunu: #17307.

Azure Cosmos DB modelindeki varlık türleri artık analiz deposu için varsayılan yaşam süresi ve yaşam süresi ile yapılandırılabilir. Örnek:

modelBuilder.Entity<Family>(
    entityTypeBuilder =>
    {
        entityTypeBuilder.HasDefaultTimeToLive(100);
        entityTypeBuilder.HasAnalyticalStoreTimeToLive(200);
    });

HTTP istemci fabrikasını çözme

GitHub Sorunu: #21274. Bu özellik @dnperfors tarafından katkıda bulundu. Çok teşekkürler!

HttpClientFactory Azure Cosmos DB sağlayıcısı tarafından kullanılan artık açıkça ayarlanabilir. Bu özellikle test sırasında, örneğin Linux üzerinde Azure Cosmos DB öykünücüsü kullanılırken sertifika doğrulamasını atlamak için yararlı olabilir:

optionsBuilder
    .EnableSensitiveDataLogging()
    .UseCosmos(
        "https://localhost:8081",
        "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
        "PrimitiveCollections",
        cosmosOptionsBuilder =>
        {
            cosmosOptionsBuilder.HttpClientFactory(
                () => new HttpClient(
                    new HttpClientHandler
                    {
                        ServerCertificateCustomValidationCallback =
                            HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
                    }));
        });

Dekont

Azure Cosmos DB sağlayıcısı iyileştirmelerini mevcut bir uygulamaya uygulama hakkında ayrıntılı bir örnek için .NET Blogu'nda EF Core Azure Cosmos DB Sağlayıcısını Test Sürüşü için Alma bölümüne bakın.

Mevcut veritabanından yapı iskelesi geliştirmeleri

EF Core 6.0, mevcut veritabanından bir EF modelini tersine mühendislikle çevirirken çeşitli geliştirmeler içerir.

Çoka çok ilişkilerin iskelesini oluşturma

GitHub Sorunu: #22475.

EF Core 6.0 basit birleştirme tablolarını algılar ve bunlar için otomatik olarak çoka çok eşlemesi oluşturur. Örneğin, ve tablolarını Posts ve Tagsbunları bağlayan birleştirme tablosunu PostTag göz önünde bulundurun:

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]));

CREATE TABLE [PostTag] (
    [PostsId] int NOT NULL,
    [TagsId] int NOT NULL,
    CONSTRAINT [PK_PostTag] PRIMARY KEY ([PostsId], [TagsId]),
    CONSTRAINT [FK_PostTag_Posts_TagsId] FOREIGN KEY ([TagsId]) REFERENCES [Tags] ([Id]) ON DELETE CASCADE,
    CONSTRAINT [FK_PostTag_Tags_PostsId] FOREIGN KEY ([PostsId]) REFERENCES [Posts] ([Id]) ON DELETE CASCADE);

Bu tablolar komut satırından yapı iskelesi oluşturulabilir. Örnek:

dotnet ef dbcontext scaffold "Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=BloggingWithNRTs" Microsoft.EntityFrameworkCore.SqlServer

Bu, Post için bir sınıfa neden olur:

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

Tag için de bir sınıf:

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

Ama tablo için PostTag sınıf yok. Bunun yerine, çoka çok ilişkisi için yapılandırma yapı iskelesi oluşturulur:

entity.HasMany(d => d.Tags)
    .WithMany(p => p.Posts)
    .UsingEntity<Dictionary<string, object>>(
        "PostTag",
        l => l.HasOne<Tag>().WithMany().HasForeignKey("PostsId"),
        r => r.HasOne<Post>().WithMany().HasForeignKey("TagsId"),
        j =>
            {
                j.HasKey("PostsId", "TagsId");
                j.ToTable("PostTag");
                j.HasIndex(new[] { "TagsId" }, "IX_PostTag_TagsId");
            });

İskele C# null atanabilir başvuru türleri

GitHub Sorunu: #15520.

EF Core 6.0 artık bir EF modelini ve C# null atanabilir başvuru türlerini (NRTs) kullanan varlık türlerinin iskelelerini oluşturur. NRT kullanımı, kodun iskelesinin oluşturulduğu C# projesinde NRT desteği etkinleştirildiğinde otomatik olarak iskelelenir.

Örneğin, aşağıdaki Tags tabloda hem null değer atanabilir hem de boş değer atanamayan dize sütunları yer alır:

CREATE TABLE [Tags] (
  [Id] int NOT NULL IDENTITY,
  [Name] nvarchar(max) NOT NULL,
  [Description] nvarchar(max) NULL,
  CONSTRAINT [PK_Tags] PRIMARY KEY ([Id]));

Bu, oluşturulan sınıfta karşılık gelen null atanabilir ve null atanamaz dize özelliklerine neden olur:

public partial class Tag
{
    public Tag()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public string? Description { get; set; }

    public virtual ICollection<Post> Posts { get; set; }
}

Benzer şekilde, aşağıdaki Posts tablolar tabloyla Blogs gerekli bir ilişki içerir:

CREATE TABLE [Posts] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NOT NULL,
    [Contents] nvarchar(max) NOT NULL,
    [PostedOn] datetime2 NOT NULL,
    [UpdatedOn] datetime2 NULL,
    [BlogId] int NOT NULL,
    CONSTRAINT [PK_Posts] PRIMARY KEY ([Id]),
    CONSTRAINT [FK_Posts_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([Id]));

Bu, bloglar arasındaki null atanamaz (gerekli) ilişkinin iskelesini oluşturur:

public partial class Blog
{
    public Blog()
    {
        Posts = new HashSet<Post>();
    }

    public int Id { get; set; }
    public string Name { get; set; } = null!;

    public virtual ICollection<Post> Posts { get; set; }
}

Ve gönderiler:

public partial class Post
{
    public Post()
    {
        Tags = new HashSet<Tag>();
    }

    public int Id { get; set; }
    public string Title { get; set; } = null!;
    public string Contents { get; set; } = null!;
    public DateTime PostedOn { get; set; }
    public DateTime? UpdatedOn { get; set; }
    public int BlogId { get; set; }

    public virtual Blog Blog { get; set; } = null!;

    public virtual ICollection<Tag> Tags { get; set; }
}

Son olarak, oluşturulan DbContext içindeki DbSet özellikleri NRT kullanımı kolay bir şekilde oluşturulur. Örnek:

public virtual DbSet<Blog> Blogs { get; set; } = null!;
public virtual DbSet<Post> Posts { get; set; } = null!;
public virtual DbSet<Tag> Tags { get; set; } = null!;

Veritabanı açıklamaları, kod açıklamalarına yapı iskelesi oluşturulur

GitHub Sorunu: #19113. Bu özellik @ErikEJ tarafından katkıda bulundu. Çok teşekkürler!

SQL tablo ve sütunlarıyla ilgili açıklamalar artık mevcut bir SQL Server veritabanından bir EF Core modeline ters mühendislik uygularken oluşturulan varlık türlerine iskeleyle eklenmiştir.

/// <summary>
/// The Blog table.
/// </summary>
public partial class Blog
{
    /// <summary>
    /// The primary key.
    /// </summary>
    [Key]
    public int Id { get; set; }
}

LINQ sorgu geliştirmeleri

EF Core 6.0, LINQ sorgularının çevirisi ve yürütülmesinde çeşitli geliştirmeler içerir.

Geliştirilmiş GroupBy desteği

GitHub Sorunları: #12088, #13805 ve #22609.

EF Core 6.0, sorgular için GroupBy daha iyi destek içerir. Özel olarak, EF Core şimdi:

  • GroupBy'yi FirstOrDefault ve ardından bir grup üzerinde (veya benzer) çevirin
  • Bir gruptan en iyi N sonucunun seçilmesini destekler
  • İşleç uygulandıktan sonra GroupBy gezintileri genişletir

Aşağıda müşteri raporlarından gelen örnek sorgular ve SQL Server'da çevirileri verilmiştir.

Örnek 1:

var people = context.People
    .Include(e => e.Shoes)
    .GroupBy(e => e.FirstName)
    .Select(
        g => g.OrderBy(e => e.FirstName)
            .ThenBy(e => e.LastName)
            .FirstOrDefault())
    .ToList();
SELECT [t0].[Id], [t0].[Age], [t0].[FirstName], [t0].[LastName], [t0].[MiddleInitial], [t].[FirstName], [s].[Id], [s].[Age], [s].[PersonId], [s].[Style]
FROM (
    SELECT [p].[FirstName]
    FROM [People] AS [p]
    GROUP BY [p].[FirstName]
) AS [t]
LEFT JOIN (
    SELECT [t1].[Id], [t1].[Age], [t1].[FirstName], [t1].[LastName], [t1].[MiddleInitial]
    FROM (
        SELECT [p0].[Id], [p0].[Age], [p0].[FirstName], [p0].[LastName], [p0].[MiddleInitial], ROW_NUMBER() OVER(PARTITION BY [p0].[FirstName] ORDER BY [p0].[FirstName], [p0].[LastName]) AS [row]
        FROM [People] AS [p0]
    ) AS [t1]
    WHERE [t1].[row] <= 1
) AS [t0] ON [t].[FirstName] = [t0].[FirstName]
LEFT JOIN [Shoes] AS [s] ON [t0].[Id] = [s].[PersonId]
ORDER BY [t].[FirstName], [t0].[FirstName]

Örnek 2:

var group = context.People
    .Select(
        p => new
        {
            p.FirstName,
            FullName = p.FirstName + " " + p.MiddleInitial + " " + p.LastName
        })
    .GroupBy(p => p.FirstName)
    .Select(g => g.First())
    .First();
SELECT [t0].[FirstName], [t0].[FullName], [t0].[c]
FROM (
    SELECT TOP(1) [p].[FirstName]
    FROM [People] AS [p]
    GROUP BY [p].[FirstName]
) AS [t]
LEFT JOIN (
    SELECT [t1].[FirstName], [t1].[FullName], [t1].[c]
    FROM (
        SELECT [p0].[FirstName], (((COALESCE([p0].[FirstName], N'') + N' ') + COALESCE([p0].[MiddleInitial], N'')) + N' ') + COALESCE([p0].[LastName], N'') AS [FullName], 1 AS [c], ROW_NUMBER() OVER(PARTITION BY [p0].[FirstName] ORDER BY [p0].[FirstName]) AS [row]
        FROM [People] AS [p0]
    ) AS [t1]
    WHERE [t1].[row] <= 1
) AS [t0] ON [t].[FirstName] = [t0].[FirstName]

Örnek 3:

var people = context.People
    .Where(e => e.MiddleInitial == "Q" && e.Age == 20)
    .GroupBy(e => e.LastName)
    .Select(g => g.First().LastName)
    .OrderBy(e => e.Length)
    .ToList();
SELECT (
    SELECT TOP(1) [p1].[LastName]
    FROM [People] AS [p1]
    WHERE (([p1].[MiddleInitial] = N'Q') AND ([p1].[Age] = 20)) AND (([p].[LastName] = [p1].[LastName]) OR ([p].[LastName] IS NULL AND [p1].[LastName] IS NULL)))
FROM [People] AS [p]
WHERE ([p].[MiddleInitial] = N'Q') AND ([p].[Age] = 20)
GROUP BY [p].[LastName]
ORDER BY CAST(LEN((
    SELECT TOP(1) [p1].[LastName]
    FROM [People] AS [p1]
    WHERE (([p1].[MiddleInitial] = N'Q') AND ([p1].[Age] = 20)) AND (([p].[LastName] = [p1].[LastName]) OR ([p].[LastName] IS NULL AND [p1].[LastName] IS NULL)))) AS int)

Örnek 4:

var results = (from person in context.People
               join shoes in context.Shoes on person.Age equals shoes.Age
               group shoes by shoes.Style
               into people
               select new
               {
                   people.Key,
                   Style = people.Select(p => p.Style).FirstOrDefault(),
                   Count = people.Count()
               })
    .ToList();
SELECT [s].[Style] AS [Key], (
    SELECT TOP(1) [s0].[Style]
    FROM [People] AS [p0]
    INNER JOIN [Shoes] AS [s0] ON [p0].[Age] = [s0].[Age]
    WHERE ([s].[Style] = [s0].[Style]) OR ([s].[Style] IS NULL AND [s0].[Style] IS NULL)) AS [Style], COUNT(*) AS [Count]
FROM [People] AS [p]
INNER JOIN [Shoes] AS [s] ON [p].[Age] = [s].[Age]
GROUP BY [s].[Style]

Örnek 5:

var results = context.People
    .GroupBy(e => e.FirstName)
    .Select(g => g.First().LastName)
    .OrderBy(e => e)
    .ToList();
SELECT (
    SELECT TOP(1) [p1].[LastName]
    FROM [People] AS [p1]
    WHERE ([p].[FirstName] = [p1].[FirstName]) OR ([p].[FirstName] IS NULL AND [p1].[FirstName] IS NULL))
FROM [People] AS [p]
GROUP BY [p].[FirstName]
ORDER BY (
    SELECT TOP(1) [p1].[LastName]
    FROM [People] AS [p1]
    WHERE ([p].[FirstName] = [p1].[FirstName]) OR ([p].[FirstName] IS NULL AND [p1].[FirstName] IS NULL))

Örnek 6:

var results = context.People.Where(e => e.Age == 20)
    .GroupBy(e => e.Id)
    .Select(g => g.First().MiddleInitial)
    .OrderBy(e => e)
    .ToList();
SELECT (
    SELECT TOP(1) [p1].[MiddleInitial]
    FROM [People] AS [p1]
    WHERE ([p1].[Age] = 20) AND ([p].[Id] = [p1].[Id]))
FROM [People] AS [p]
WHERE [p].[Age] = 20
GROUP BY [p].[Id]
ORDER BY (
    SELECT TOP(1) [p1].[MiddleInitial]
    FROM [People] AS [p1]
    WHERE ([p1].[Age] = 20) AND ([p].[Id] = [p1].[Id]))

Örnek 7:

var size = 11;
var results
    = context.People
        .Where(
            p => p.Feet.Size == size
                 && p.MiddleInitial != null
                 && p.Feet.Id != 1)
        .GroupBy(
            p => new
            {
                p.Feet.Size,
                p.Feet.Person.LastName
            })
        .Select(
            g => new
            {
                g.Key.LastName,
                g.Key.Size,
                Min = g.Min(p => p.Feet.Size),
            })
        .ToList();
Executed DbCommand (12ms) [Parameters=[@__size_0='11'], CommandType='Text', CommandTimeout='30']
SELECT [p0].[LastName], [f].[Size], MIN([f0].[Size]) AS [Min]
FROM [People] AS [p]
LEFT JOIN [Feet] AS [f] ON [p].[Id] = [f].[Id]
LEFT JOIN [People] AS [p0] ON [f].[Id] = [p0].[Id]
LEFT JOIN [Feet] AS [f0] ON [p].[Id] = [f0].[Id]
WHERE (([f].[Size] = @__size_0) AND [p].[MiddleInitial] IS NOT NULL) AND (([f].[Id] <> 1) OR [f].[Id] IS NULL)
GROUP BY [f].[Size], [p0].[LastName]

Örnek 8:

var result = context.People
    .Include(x => x.Shoes)
    .Include(x => x.Feet)
    .GroupBy(
        x => new
        {
            x.Feet.Id,
            x.Feet.Size
        })
    .Select(
        x => new
        {
            Key = x.Key.Id + x.Key.Size,
            Count = x.Count(),
            Sum = x.Sum(el => el.Id),
            SumOver60 = x.Sum(el => el.Id) / (decimal)60,
            TotalCallOutCharges = x.Sum(el => el.Feet.Size == 11 ? 1 : 0)
        })
    .Count();
SELECT COUNT(*)
FROM (
    SELECT [f].[Id], [f].[Size]
    FROM [People] AS [p]
    LEFT JOIN [Feet] AS [f] ON [p].[Id] = [f].[Id]
    GROUP BY [f].[Id], [f].[Size]
) AS [t]

Örnek 9:

var results = context.People
    .GroupBy(n => n.FirstName)
    .Select(g => new
    {
        Feet = g.Key,
        Total = g.Sum(n => n.Feet.Size)
    })
    .ToList();
SELECT [p].[FirstName] AS [Feet], COALESCE(SUM([f].[Size]), 0) AS [Total]
FROM [People] AS [p]
LEFT JOIN [Feet] AS [f] ON [p].[Id] = [f].[Id]
GROUP BY [p].[FirstName]

Örnek 10:

var results = from Person person1
                  in from Person person2
                         in context.People
                     select person2
              join Shoes shoes
                  in context.Shoes
                  on person1.Age equals shoes.Age
              group shoes by
                  new
                  {
                      person1.Id,
                      shoes.Style,
                      shoes.Age
                  }
              into temp
              select
                  new
                  {
                      temp.Key.Id,
                      temp.Key.Age,
                      temp.Key.Style,
                      Values = from t
                                   in temp
                               select
                                   new
                                   {
                                       t.Id,
                                       t.Style,
                                       t.Age
                                   }
                  };
SELECT [t].[Id], [t].[Age], [t].[Style], [t0].[Id], [t0].[Style], [t0].[Age], [t0].[Id0]
FROM (
    SELECT [p].[Id], [s].[Age], [s].[Style]
    FROM [People] AS [p]
    INNER JOIN [Shoes] AS [s] ON [p].[Age] = [s].[Age]
    GROUP BY [p].[Id], [s].[Style], [s].[Age]
) AS [t]
LEFT JOIN (
    SELECT [s0].[Id], [s0].[Style], [s0].[Age], [p0].[Id] AS [Id0]
    FROM [People] AS [p0]
    INNER JOIN [Shoes] AS [s0] ON [p0].[Age] = [s0].[Age]
) AS [t0] ON (([t].[Id] = [t0].[Id0]) AND (([t].[Style] = [t0].[Style]) OR ([t].[Style] IS NULL AND [t0].[Style] IS NULL))) AND ([t].[Age] = [t0].[Age])
ORDER BY [t].[Id], [t].[Style], [t].[Age], [t0].[Id0]

Örnek 11:

var grouping = context.People
    .GroupBy(i => i.LastName)
    .Select(g => new { LastName = g.Key, Count = g.Count() , First = g.FirstOrDefault(), Take = g.Take(2)})
    .OrderByDescending(e => e.LastName)
    .ToList();
SELECT [t].[LastName], [t].[c], [t0].[Id], [t2].[Id], [t2].[Age], [t2].[FirstName], [t2].[LastName], [t2].[MiddleInitial], [t0].[Age], [t0].[FirstName], [t0].[LastName], [t0].[MiddleInitial]
FROM (
    SELECT [p].[LastName], COUNT(*) AS [c]
    FROM [People] AS [p]
    GROUP BY [p].[LastName]
) AS [t]
LEFT JOIN (
    SELECT [t1].[Id], [t1].[Age], [t1].[FirstName], [t1].[LastName], [t1].[MiddleInitial]
    FROM (
        SELECT [p0].[Id], [p0].[Age], [p0].[FirstName], [p0].[LastName], [p0].[MiddleInitial], ROW_NUMBER() OVER(PARTITION BY [p0].[LastName] ORDER BY [p0].[Id]) AS [row]
        FROM [People] AS [p0]
    ) AS [t1]
    WHERE [t1].[row] <= 1
) AS [t0] ON [t].[LastName] = [t0].[LastName]
LEFT JOIN (
    SELECT [t3].[Id], [t3].[Age], [t3].[FirstName], [t3].[LastName], [t3].[MiddleInitial]
    FROM (
        SELECT [p1].[Id], [p1].[Age], [p1].[FirstName], [p1].[LastName], [p1].[MiddleInitial], ROW_NUMBER() OVER(PARTITION BY [p1].[LastName] ORDER BY [p1].[Id]) AS [row]
        FROM [People] AS [p1]
    ) AS [t3]
    WHERE [t3].[row] <= 2
) AS [t2] ON [t].[LastName] = [t2].[LastName]
ORDER BY [t].[LastName] DESC, [t0].[Id], [t2].[LastName], [t2].[Id]

Örnek 12:

var grouping = context.People
    .Include(e => e.Shoes)
    .OrderBy(e => e.FirstName)
    .ThenBy(e => e.LastName)
    .GroupBy(e => e.FirstName)
    .Select(g => new { Name = g.Key, People = g.ToList()})
    .ToList();
SELECT [t].[FirstName], [t0].[Id], [t0].[Age], [t0].[FirstName], [t0].[LastName], [t0].[MiddleInitial], [t0].[Id0], [t0].[Age0], [t0].[PersonId], [t0].[Style]
FROM (
    SELECT [p].[FirstName]
    FROM [People] AS [p]
    GROUP BY [p].[FirstName]
) AS [t]
LEFT JOIN (
    SELECT [p0].[Id], [p0].[Age], [p0].[FirstName], [p0].[LastName], [p0].[MiddleInitial], [s].[Id] AS [Id0], [s].[Age] AS [Age0], [s].[PersonId], [s].[Style]
    FROM [People] AS [p0]
    LEFT JOIN [Shoes] AS [s] ON [p0].[Id] = [s].[PersonId]
) AS [t0] ON [t].[FirstName] = [t0].[FirstName]
ORDER BY [t].[FirstName], [t0].[Id]

Örnek 13:

var grouping = context.People
    .GroupBy(m => new {m.FirstName, m.MiddleInitial })
    .Select(am => new
    {
        Key = am.Key,
        Items = am.ToList()
    })
    .ToList();
SELECT [t].[FirstName], [t].[MiddleInitial], [p0].[Id], [p0].[Age], [p0].[FirstName], [p0].[LastName], [p0].[MiddleInitial]
FROM (
    SELECT [p].[FirstName], [p].[MiddleInitial]
    FROM [People] AS [p]
    GROUP BY [p].[FirstName], [p].[MiddleInitial]
) AS [t]
LEFT JOIN [People] AS [p0] ON (([t].[FirstName] = [p0].[FirstName]) OR ([t].[FirstName] IS NULL AND [p0].[FirstName] IS NULL)) AND (([t].[MiddleInitial] = [p0].[MiddleInitial]) OR ([t].[MiddleInitial] IS NULL AND [p0].[MiddleInitial] IS NULL))
ORDER BY [t].[FirstName], [t].[MiddleInitial]

Model

Bu örnekler için kullanılan varlık türleri şunlardır:

public class Person
{
    public int Id { get; set; }
    public int Age { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string MiddleInitial { get; set; }
    public Feet Feet { get; set; }
    public ICollection<Shoes> Shoes { get; } = new List<Shoes>();
}

public class Shoes
{
    public int Id { get; set; }
    public int Age { get; set; }
    public string Style { get; set; }
    public Person Person { get; set; }
}

public class Feet
{
    public int Id { get; set; }
    public int Size { get; set; }
    public Person Person { get; set; }
}

String.Concat'i birden çok bağımsız değişkenle çevirme

GitHub Sorunu: #23859. Bu özellik @wmeints tarafından katkıda bulundu. Çok teşekkürler!

EF Core 6.0'dan başlayarak, birden çok bağımsız değişken içeren öğesine String.Concat yapılan çağrılar artık SQL'e çevrilir. Örneğin, aşağıdaki sorgu:

var shards = context.Shards
    .Where(e => string.Concat(e.Token1, e.Token2, e.Token3) != e.TokensProcessed).ToList();

SQL Server kullanılırken aşağıdaki SQL'e çevrilir:

SELECT [s].[Id], [s].[Token1], [s].[Token2], [s].[Token3], [s].[TokensProcessed]
FROM [Shards] AS [s]
WHERE (([s].[Token1] + ([s].[Token2] + [s].[Token3])) <> [s].[TokensProcessed]) OR [s].[TokensProcessed] IS NULL

System.Linq.Async ile daha sorunsuz tümleştirme

GitHub Sorunu: #24041.

System.Linq.Async paketi istemci tarafı zaman uyumsuz LINQ işlemesi ekler. Zaman uyumsuz LINQ yöntemleri için ad alanı çakışması nedeniyle bu paketi EF Core'un önceki sürümleriyle kullanmak zahmetliydi. EF Core 6.0'da kullanıma sunulan EF Core'un DbSet<TEntity> arabirimi doğrudan uygulaması gerekmeyecek şekilde C# desen eşleştirme IAsyncEnumerable<T> özelliğinden yararlandık.

EF Core sorguları genellikle sunucuda tamamen çevrildiğinden çoğu uygulamanın System.Linq.Async kullanması gerekmediğini unutmayın.

GitHub Sorunu: #23921.

EF Core 6.0'da ve Containsiçin FreeText(DbFunctions, String, String) parametre gereksinimlerini gevşetdik. Bu, bu işlevlerin ikili sütunlarla veya değer dönüştürücü kullanılarak eşlenen sütunlarla kullanılmasını sağlar. Örneğin, değer nesnesi olarak tanımlanmış bir özelliğe sahip bir Name varlık türünü düşünün:

public class Customer
{
    public int Id { get; set; }

    public Name Name{ get; set; }
}

public class Name
{
    public string First { get; set; }
    public string MiddleInitial { get; set; }
    public string Last { get; set; }
}

Bu, veritabanındaki JSON ile eşlenir:

modelBuilder.Entity<Customer>()
    .Property(e => e.Name)
    .HasConversion(
        v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
        v => JsonSerializer.Deserialize<Name>(v, (JsonSerializerOptions)null));

Bir sorgu artık kullanılarak Contains veya FreeText özelliğinin Name türü olmasa stringbile yürütülebilir. Örnek:

var result = context.Customers.Where(e => EF.Functions.Contains(e.Name, "Martin")).ToList();

Bu, SQL Server kullanılırken aşağıdaki SQL'i oluşturur:

SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE CONTAINS([c].[Name], N'Martin')

SQLite'te ToString'i çevir

GitHub Sorunu: #17223. Bu özellik @ralmsdeveloper tarafından katkıda bulundu. Çok teşekkürler!

ToString() çağrısı artık SQLite veritabanı sağlayıcısı kullanılırken SQL'e çevriliyor. Bu, dize olmayan sütunlar içeren metin aramaları için yararlı olabilir. Örneğin, telefon numaralarını sayısal değerler olarak depolayan bir User varlık türü düşünün:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public long PhoneNumber { get; set; }
}

ToString , sayıyı veritabanındaki bir dizeye dönüştürmek için kullanılabilir. Daha sonra bu dizeyi bir desenle eşleşen sayıları bulmak için gibi LIKE bir işlevle kullanabiliriz. Örneğin, 555 içeren tüm sayıları bulmak için:

var users = context.Users.Where(u => EF.Functions.Like(u.PhoneNumber.ToString(), "%555%")).ToList();

Bu, SQLite veritabanı kullanılırken aşağıdaki SQL'e çevrilir:

SELECT "u"."Id", "u"."PhoneNumber", "u"."Username"
FROM "Users" AS "u"
WHERE CAST("u"."PhoneNumber" AS TEXT) LIKE '%555%'

SQL Server için çevirisinin ToString() EF Core 5.0'da zaten desteklendiğini ve diğer veritabanı sağlayıcıları tarafından da desteklendiğini unutmayın.

EF. Functions.Random

GitHub Sorunu: #16141. Bu özellik @RaymondHuy tarafından katkıda bulundu. Çok teşekkürler!

EF.Functions.Random 0 ile 1 arasında özel bir sahte rastgele sayı döndüren bir veritabanı işleviyle eşler. Çeviriler SQL Server, SQLite ve Azure Cosmos DB için EF Core deposunda uygulanmıştır. Örneğin, özelliği olan Popularity bir User varlık türünü göz önünde bulundurun:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    public int Popularity { get; set; }
}

Popularity 1 ile 5 (dahil) arasında değerlere sahip olabilir. Kullanarak EF.Functions.Random , rastgele seçilen popülerliğe sahip tüm kullanıcıları döndürmek için bir sorgu yazabiliriz:

var users = context.Users.Where(u => u.Popularity == (int)(EF.Functions.Random() * 4.0) + 1).ToList();

Bu, SQL Server veritabanı kullanılırken aşağıdaki SQL'e çevrilir:

SELECT [u].[Id], [u].[Popularity], [u].[Username]
FROM [Users] AS [u]
WHERE [u].[Popularity] = (CAST((RAND() * 4.0E0) AS int) + 1)

IsNullOrWhitespace için geliştirilmiş SQL Server çevirisi

GitHub Sorunu: #22916. Bu özellik @Marusyk tarafından katkıda bulundu. Çok teşekkürler!

Aşağıdaki sorguyu göz önünde bulundurun:

var users = context.Users.Where(
    e => string.IsNullOrWhiteSpace(e.FirstName)
         || string.IsNullOrWhiteSpace(e.LastName)).ToList();

EF Core 6.0'ın öncesinde bu, SQL Server'da aşağıdakine çevrildi:

SELECT [u].[Id], [u].[FirstName], [u].[LastName]
FROM [Users] AS [u]
WHERE ([u].[FirstName] IS NULL OR (LTRIM(RTRIM([u].[FirstName])) = N'')) OR ([u].[LastName] IS NULL OR (LTRIM(RTRIM([u].[LastName])) = N''))

Bu çeviri, EF Core 6.0 için şu şekilde geliştirilmiştir:

SELECT [u].[Id], [u].[FirstName], [u].[LastName]
FROM [Users] AS [u]
WHERE ([u].[FirstName] IS NULL OR ([u].[FirstName] = N'')) OR ([u].[LastName] IS NULL OR ([u].[LastName] = N''))

Bellek içi sağlayıcı için sorgu tanımlama

GitHub Sorunu: #24600.

Yeni bir yöntem ToInMemoryQuery , belirli bir varlık türü için bellek içi veritabanına yönelik bir tanımlama sorgusu yazmak için kullanılabilir. Bu, özellikle de bu görünümler anahtarsız varlık türleri döndürdiğinde bellek içi veritabanında görünümlerin eşdeğerini oluşturmak için kullanışlıdır. Örneğin, Birleşik Krallık'ta bulunan müşteriler için bir müşteri veritabanı düşünün. Her müşterinin bir adresi vardır:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public int Id { get; set; }
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Şimdi, her posta kodu alanında kaç müşteri olduğunu gösteren bu veriler üzerinde bir görünüm istediğimizi düşünün. Bunu temsil etmek için anahtarsız bir varlık türü oluşturabiliriz:

public class CustomerDensity
{
    public string Postcode { get; set; }
    public int CustomerCount { get; set; }
}

DbContext'te bunun için bir DbSet özelliği ve diğer üst düzey varlık türleri için kümeler tanımlayın:

public DbSet<Customer> Customers { get; set; }
public DbSet<CustomerDensity> CustomerDensities { get; set; }

Ardından içinde, OnModelCreatingiçin CustomerDensitiesdöndürülecek verileri tanımlayan bir LINQ sorgusu yazabiliriz:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<CustomerDensity>()
        .HasNoKey()
        .ToInMemoryQuery(
            () => Customers
                .GroupBy(c => c.Address.Postcode.Substring(0, 3))
                .Select(
                    g =>
                        new CustomerDensity
                        {
                            Postcode = g.Key,
                            CustomerCount = g.Count()
                        }));
}

Bu daha sonra diğer dbset özellikleri gibi sorgulanabilir:

var results = context.CustomerDensities.ToList();

Alt Dizeyi tek parametreyle çevirme

GitHub Sorunu: #20173. Bu özellik @stevendarby tarafından katkıda bulundu. Çok teşekkürler!

EF Core 6.0 artık kullanımlarını string.Substring tek bir bağımsız değişkenle çevirir. Örnek:

var result = context.Customers
    .Select(a => new { Name = a.Name.Substring(3) })
    .FirstOrDefault(a => a.Name == "hur");

Bu, SQL Server kullanılırken aşağıdaki SQL'e çevrilir:

SELECT TOP(1) SUBSTRING([c].[Name], 3 + 1, LEN([c].[Name])) AS [Name]
FROM [Customers] AS [c]
WHERE SUBSTRING([c].[Name], 3 + 1, LEN([c].[Name])) = N'hur'

Gezinti dışı koleksiyonlar için bölünmüş sorgular

GitHub Sorunu: #21234.

EF Core, tek bir LINQ sorgusunu birden çok SQL sorgusuna bölmeyi destekler. EF Core 6.0'da bu destek, gezinti dışı koleksiyonların sorgu projeksiyonunda yer aldığı durumları içerecek şekilde genişletilmiştir.

Aşağıda, SQL Server'da tek bir sorguya veya birden çok sorguya çeviriyi gösteren örnek sorgular verilmiştır.

Örnek 1:

LINQ sorgusu:

context.Customers
    .Select(
        c => new
        {
            c,
            Orders = c.Orders
                .Where(o => o.Id > 1)
        })
    .ToList();

Tek SQL sorgusu:

SELECT [c].[Id], [t].[Id], [t].[CustomerId], [t].[OrderDate]
FROM [Customers] AS [c]
LEFT JOIN (
    SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate]
    FROM [Order] AS [o]
    WHERE [o].[Id] > 1
) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id]

Birden çok SQL sorgusu:

SELECT [c].[Id]
FROM [Customers] AS [c]
ORDER BY [c].[Id]

SELECT [t].[Id], [t].[CustomerId], [t].[OrderDate], [c].[Id]
FROM [Customers] AS [c]
INNER JOIN (
    SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate]
    FROM [Order] AS [o]
    WHERE [o].[Id] > 1
) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id]

Örnek 2:

LINQ sorgusu:

context.Customers
    .Select(
        c => new
        {
            c,
            OrderDates = c.Orders
                .Where(o => o.Id > 1)
                .Select(o => o.OrderDate)
        })
    .ToList();

Tek SQL sorgusu:

SELECT [c].[Id], [t].[OrderDate], [t].[Id]
FROM [Customers] AS [c]
  LEFT JOIN (
  SELECT [o].[OrderDate], [o].[Id], [o].[CustomerId]
  FROM [Order] AS [o]
  WHERE [o].[Id] > 1
  ) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id]

Birden çok SQL sorgusu:

SELECT [c].[Id]
FROM [Customers] AS [c]
ORDER BY [c].[Id]

SELECT [t].[Id], [t].[CustomerId], [t].[OrderDate], [c].[Id]
FROM [Customers] AS [c]
INNER JOIN (
    SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate]
    FROM [Order] AS [o]
    WHERE [o].[Id] > 1
) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id]

Örnek 3:

LINQ sorgusu:

context.Customers
    .Select(
        c => new
        {
            c,
            OrderDates = c.Orders
                .Where(o => o.Id > 1)
                .Select(o => o.OrderDate)
                .Distinct()
        })
    .ToList();

Tek SQL sorgusu:

SELECT [c].[Id], [t].[OrderDate]
FROM [Customers] AS [c]
  OUTER APPLY (
  SELECT DISTINCT [o].[OrderDate]
  FROM [Order] AS [o]
  WHERE ([c].[Id] = [o].[CustomerId]) AND ([o].[Id] > 1)
  ) AS [t]
ORDER BY [c].[Id]

Birden çok SQL sorgusu:

SELECT [c].[Id]
FROM [Customers] AS [c]
ORDER BY [c].[Id]

SELECT [t].[OrderDate], [c].[Id]
FROM [Customers] AS [c]
  CROSS APPLY (
  SELECT DISTINCT [o].[OrderDate]
  FROM [Order] AS [o]
  WHERE ([c].[Id] = [o].[CustomerId]) AND ([o].[Id] > 1)
  ) AS [t]
ORDER BY [c].[Id]

Koleksiyona katılırken son ORDER BY yan tümcesini kaldırma

GitHub Sorunu: #19828.

İlgili bire çok varlıkları yüklerken EF Core, belirli bir varlığa ilişkin tüm ilgili varlıkların birlikte gruplandığından emin olmak için ORDER BY yan tümceleri ekler. Ancak, EF'in gerekli gruplandırmaları oluşturması için son ORDER BY yan tümcesi gerekli değildir ve performansı etkileyebilir. Bu nedenle, EF Core 6.0 bu yan tümcesi kaldırılır.

Örneğin, şu sorguyu göz önünde bulundurun:

context.Customers
    .Select(
        e => new
        {
            e.Id,
            FirstOrder = e.Orders.Where(i => i.Id == 1).ToList()
        })
    .ToList();

SQL Server'da EF Core 5.0 ile bu sorgu şu şekilde çevrilir:

SELECT [c].[Id], [t].[Id], [t].[CustomerId], [t].[OrderDate]
FROM [Customers] AS [c]
LEFT JOIN (
    SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate]
    FROM [Order] AS [o]
    WHERE [o].[Id] = 1
) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id], [t].[Id]

EF Core 6.0 ile bunun yerine şu şekilde çevrilir:

SELECT [c].[Id], [t].[Id], [t].[CustomerId], [t].[OrderDate]
FROM [Customers] AS [c]
LEFT JOIN (
    SELECT [o].[Id], [o].[CustomerId], [o].[OrderDate]
    FROM [Order] AS [o]
    WHERE [o].[Id] = 1
) AS [t] ON [c].[Id] = [t].[CustomerId]
ORDER BY [c].[Id]

Sorguları dosya adı ve satır numarasıyla etiketleme

GitHub Sorunu: #14176. Bu özellik @michalczerwinski tarafından katkıda bulundu. Çok teşekkürler!

Sorgu etiketleri, linq sorgusuna metin etiketi eklemeye olanak sağlar; böylece daha sonra oluşturulan SQL'e eklenir. EF Core 6.0'da bu, sorguları LINQ kodunun dosya adı ve satır numarasıyla etiketlemek için kullanılabilir. Örnek:

var results1 = context
    .Customers
    .TagWithCallSite()
    .Where(c => c.Name.StartsWith("A"))
    .ToList();

Bu, SQL Server kullanılırken aşağıdaki oluşturulan SQL'e neden olur:

-- file: C:\dotnet\efdocs\samples\core\Miscellaneous\NewInEFCore6\TagWithFileAndLineSample.cs:21

SELECT [c].[Id], [c].[Name]
FROM [Customers] AS [c]
WHERE [c].[Name] IS NOT NULL AND ([c].[Name] LIKE N'A%')

Sahip olunan isteğe bağlı bağımlı işlemede yapılan değişiklikler

GitHub Sorunu: #24558.

bir tabloyu asıl varlığıyla paylaştığında isteğe bağlı bir bağımlı varlığın var olup olmadığını bilmek zorlaşır. Bunun nedeni, bağımlı için tabloda bir satır olmasıdır çünkü bağımlının var olup olmamasına bakılmaksızın sorumlunun buna ihtiyacı vardır. Bunu kesin bir şekilde işlemenin yolu, bağımlının en az bir gerekli özelliğe sahip olduğundan emin olmaktır. Gerekli bir özellik null olamayacağından, bu özellik için sütundaki değerin null olması, bağımlı varlığın mevcut olmadığı anlamına gelir.

Örneğin, her müşterinin sahip Addressolduğu bir Customer sınıfı düşünün:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }

    [Required]
    public string Postcode { get; set; }
}

Adres isteğe bağlıdır; başka bir deyişle, adresi olmayan bir müşteriyi kaydetmek geçerli olur:

context.Customers1.Add(
    new()
    {
        Name = "Foul Ole Ron"
    });

Ancak müşterinin adresi varsa, bu adresin en az null olmayan bir posta kodu olmalıdır:

context.Customers1.Add(
    new()
    {
        Name = "Havelock Vetinari",
        Address = new()
        {
            Postcode = "AN1 1PL",
        }
    });

Bu, özelliği olarak Requiredişaretlenerek Postcode sağlanır.

Şimdi müşteriler sorgulandığında Postcode sütunu null ise, bu müşterinin bir adresi olmadığı ve gezinti özelliğinin Customer.Address null bırakılıp bırakılmadığını gösterir. Örneğin, müşteriler aracılığıyla yineleme ve Adres değerinin null olup olmadığını denetleme:

foreach (var customer in context.Customers1)
{
    Console.Write(customer.Name);

    if (customer.Address == null)
    {
        Console.WriteLine(" has no address.");
    }
    else
    {
        Console.WriteLine($" has postcode {customer.Address.Postcode}.");
    }
}

Aşağıdaki sonuçları oluşturur:

Foul Ole Ron has no address.
Havelock Vetinari has postcode AN1 1PL.

Bunun yerine, adres dışında hiçbir özelliğin gerekli olmadığı durumu göz önünde bulundurun:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Şimdi hem adresi olmayan bir müşteriyi hem de tüm adres özelliklerinin null olduğu bir adrese sahip bir müşteriyi kaydetmek mümkündür:

context.Customers2.Add(
    new()
    {
        Name = "Foul Ole Ron"
    });

context.Customers2.Add(
    new()
    {
        Name = "Havelock Vetinari",
        Address = new()
    });

Ancak veritabanı sütunlarını doğrudan sorgulayarak görebileceğiniz gibi, veritabanında bu iki durum ayırt edilemez:

Id  Name               House   Street  City    Postcode
1   Foul Ole Ron       NULL    NULL    NULL    NULL
2   Havelock Vetinari  NULL    NULL    NULL    NULL

Bu nedenle EF Core 6.0 artık tüm özelliklerinin null olduğu isteğe bağlı bir bağımlıyı kaydederken sizi uyaracaktır. Örnek:

warn: 27/9/2021 09:25:01.338 RelationalEventId.OptionalDependentWithAllNullPropertiesWarning[20704] (Microsoft.EntityFrameworkCore.Update) Birincil anahtar değerleri {CustomerId: -2147482646} olan 'Address' türündeki varlık, tablo paylaşımı kullanan isteğe bağlı bir bağımlıdır. Varlığın var olup olmadığını belirlemek için varsayılan olmayan değere sahip herhangi bir özelliği yoktur. Bu, sorgulandığında tüm özellikleri varsayılan değerlere ayarlanmış bir örnek yerine hiçbir nesne örneğinin oluşturulacağı anlamına gelir. İç içe bağımlılar da kaybolur. Yalnızca varsayılan değerlerle herhangi bir örneği kaydetmeyin veya gelen gezintiyi modelde gerektiği gibi işaretleyin.

Bu, isteğe bağlı bağımlı olanın kendisi de aynı tabloya eşlenmiş daha isteğe bağlı bir bağımlı için bir sorumlu gibi davrandığı daha da karmaşık hale gelir. EF Core 6.0 yalnızca uyarı yerine yalnızca iç içe yerleştirilmiş isteğe bağlı bağımlı durumlara izin vermemektedir. Örneğin, sahip olduğu ve AddressContactInfosahip olduğu ContactInfoCustomer aşağıdaki modeli göz önünde bulundurun:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ContactInfo ContactInfo { get; set; }
}

public class ContactInfo
{
    public string Phone { get; set; }
    public Address Address { get; set; }
}

public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Şimdi null ise ContactInfo.Phone , adresin Address kendisinde veri bulunsa bile EF Core ilişki isteğe bağlıysa örneğini oluşturmaz. Bu tür bir model için EF Core 6.0 aşağıdaki özel durumu oluşturur:

System.InvalidOperationException: Varlık türü 'ContactInfo', varlığın var olup olmadığını belirlemek için gerekli olmayan paylaşılan özellik olmadan tablo paylaşımı ve diğer bağımlıları içeren isteğe bağlı bir bağımlıdır. Tüm null atanabilir özellikler veritabanında null değer içeriyorsa, sorguda iç içe bağımlı değerlerin kaybolmasına neden olan bir nesne örneği oluşturulmaz. Diğer özellikler için null değerlere sahip örnekler oluşturmak için gerekli bir özellik ekleyin veya her zaman örnek oluşturmak için gelen gezintiyi gerekli olarak işaretleyin.

Buradaki alt çizgi, isteğe bağlı bir bağımlının tüm null atanabilir özellik değerlerini içerebileceği ve bir tabloyu sorumlusuyla paylaşabileceği durumdan kaçınmaktır. Bunu önlemenin üç kolay yolu vardır:

  1. Bağımlıyı gerekli hale getirin. Bu, tüm özellikleri null olsa bile, bağımlı varlığın sorgulandıktan sonra her zaman bir değere sahip olacağı anlamına gelir.
  2. Yukarıda açıklandığı gibi, bağımlının en az bir gerekli özellik içerdiğinden emin olun.
  3. İsteğe bağlı bağımlıları sorumluyla paylaşmak yerine kendi tablolarına kaydedin.

Bir bağımlı, gezintisinde Required özniteliği kullanılarak gerekli hale getirilebilir:

public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Required]
    public Address Address { get; set; }
}

public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }
    public string Postcode { get; set; }
}

Veya içinde gerekli olduğunu belirterek:OnModelCreating

modelBuilder.Entity<WithRequiredNavigation.Customer>(
    b =>
        {
            b.OwnsOne(e => e.Address);
            b.Navigation(e => e.Address).IsRequired();
        });

Bağımlılar, içinde OnModelCreatingkullanılacak tablolar belirtilerek farklı bir tabloya kaydedilebilir:

modelBuilder
    .Entity<WithDifferentTable.Customer>(
        b =>
            {
                b.ToTable("Customers");
                b.OwnsOne(
                    e => e.Address,
                    b => b.ToTable("CustomerAddresses"));
            });

İsteğe bağlı bağımlıların iç içe yerleştirilmiş isteğe bağlı bağımlı örnekleri de dahil olmak üzere daha fazla örnek için GitHub'daki OptionalDependentsSample bölümüne bakın.

Yeni eşleme öznitelikleri

EF Core 6.0, veritabanına eşlenme biçimini değiştirmek için koda uygulanabilen birkaç yeni öznitelik içerir.

UnicodeAttribute

GitHub Sorunu: #19794. Bu özellik @RaymondHuy tarafından katkıda bulundu. Çok teşekkürler!

EF Core 6.0'dan başlayarak, bir dize özelliği artık veritabanı türü doğrudan belirtilmeden eşleme özniteliği kullanılarak Unicode olmayan bir sütuna eşlenebilir. Örneğin, Uluslararası Standart Kitap Numarası (ISBN) için "ISBN 978-3-16-148410-0" biçiminde bir özelliği olan bir varlık türü düşününBook:

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }

    [Unicode(false)]
    [MaxLength(22)]
    public string Isbn { get; set; }
}

ISBN'ler unicode olmayan karakterler içeremediğinden Unicode , özniteliği Unicode olmayan bir dize türünün kullanılmasına neden olur. Ayrıca, MaxLength veritabanı sütununun boyutunu sınırlamak için kullanılır. Örneğin, SQL Server kullanılırken bunun sonucunda veritabanı sütunu şöyle varchar(22)olur:

CREATE TABLE [Book] (
    [Id] int NOT NULL IDENTITY,
    [Title] nvarchar(max) NULL,
    [Isbn] varchar(22) NULL,
    CONSTRAINT [PK_Book] PRIMARY KEY ([Id]));

Dekont

EF Core, dize özelliklerini varsayılan olarak Unicode sütunlarıyla eşler. UnicodeAttribute veritabanı sistemi yalnızca Unicode türlerini desteklediğinde yoksayılır.

PrecisionAttribute

GitHub Sorunu: #17914. Bu özellik @RaymondHuy tarafından katkıda bulundu. Çok teşekkürler!

Veritabanı sütununun duyarlığı ve ölçeği artık veritabanı türü doğrudan belirtilmeden eşleme öznitelikleri kullanılarak yapılandırılabilir. Örneğin, ondalık Price özelliği olan bir Product varlık türünü göz önünde bulundurun:

public class Product
{
    public int Id { get; set; }

    [Precision(precision: 10, scale: 2)]
    public decimal Price { get; set; }
}

EF Core, bu özelliği duyarlık 10 ve ölçek 2 olan bir veritabanı sütunuyla eşler. Örneğin, SQL Server'da:

CREATE TABLE [Product] (
    [Id] int NOT NULL IDENTITY,
    [Price] decimal(10,2) NOT NULL,
    CONSTRAINT [PK_Product] PRIMARY KEY ([Id]));

EntityTypeConfigurationAttribute

GitHub Sorunu: #23163. Bu özellik @KaloyanIT tarafından katkıda bulundu. Çok teşekkürler!

IEntityTypeConfiguration<TEntity> örnekleri, her varlık türü için yapılandırmanın kendi yapılandırma sınıfında bulunmasına izin verir ModelBuilder . Örnek:

public class BookConfiguration : IEntityTypeConfiguration<Book>
{
    public void Configure(EntityTypeBuilder<Book> builder)
    {
        builder
            .Property(e => e.Isbn)
            .IsUnicode(false)
            .HasMaxLength(22);
    }
}

Normalde, bu yapılandırma sınıfı örneği oluşturulup öğesinden DbContext.OnModelCreatingolarak çağrılmalıdır. Örnek:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    new BookConfiguration().Configure(modelBuilder.Entity<Book>());
}

EF Core 6.0'dan başlayarak, EF Core'un uygun yapılandırmayı bulup kullanabilmesi için varlık türüne yerleştirilebilir EntityTypeConfigurationAttribute . Örnek:

[EntityTypeConfiguration(typeof(BookConfiguration))]
public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Isbn { get; set; }
}

Bu öznitelik, bir modele varlık türü eklendiğinde EF Core'un belirtilen IEntityTypeConfiguration uygulamayı Book kullanacağı anlamına gelir. Varlık türü, normal mekanizmalardan biri kullanılarak bir modele eklenir. Örneğin, varlık türü için bir DbSet<TEntity> özellik oluşturarak:

public class BooksContext : DbContext
{
    public DbSet<Book> Books { get; set; }

    //...

Veya içinde kaydederek:OnModelCreating

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Book>();
}

Dekont

EntityTypeConfigurationAttribute türler bir derlemede otomatik olarak bulunmayacak. Öznitelik bu varlık türünde bulunabilmesi için önce modele varlık türleri eklenmelidir.

Model oluşturma geliştirmeleri

EF Core 6.0, yeni eşleme özniteliklerine ek olarak model oluşturma işleminde birkaç iyileştirme daha içerir.

SQL Server seyrek sütunları desteği

GitHub Sorunu: #8023.

SQL Server seyrek sütunları , null değerleri depolamak için iyileştirilmiş normal sütunlardır. Bu, nadiren kullanılan bir alt türün özelliklerinin tablodaki çoğu satır için null sütun değerlerine neden olacağı TPH devralma eşlemesi kullanılırken yararlı olabilir. Örneğin, öğesinden ForumUsergenişleten bir ForumModerator sınıf düşünün:

public class ForumUser
{
    public int Id { get; set; }
    public string Username { get; set; }
}

public class ForumModerator : ForumUser
{
    public string ForumName { get; set; }
}

Milyonlarca kullanıcı olabilir ve bunlardan yalnızca birkaçı moderatör olabilir. Bu, seyrek olarak eşlemenin ForumName burada anlamlı olabileceği anlamına gelir. Bu artık içinde OnModelCreatingkullanılarak IsSparse yapılandırılabilir. Örnek:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<ForumModerator>()
        .Property(e => e.ForumName)
        .IsSparse();
}

EF Core geçişleri sütunu seyrek olarak işaretler. Örnek:

CREATE TABLE [ForumUser] (
    [Id] int NOT NULL IDENTITY,
    [Username] nvarchar(max) NULL,
    [Discriminator] nvarchar(max) NOT NULL,
    [ForumName] nvarchar(max) SPARSE NULL,
    CONSTRAINT [PK_ForumUser] PRIMARY KEY ([Id]));

Dekont

Seyrek sütunların sınırlamaları vardır. Seyrek sütunların senaryonuz için doğru seçim olduğundan emin olmak için SQL Server seyrek sütunlar belgelerini okuduğunuzdan emin olun.

HasConversion API'sinde geliştirmeler

GitHub Sorunu: #25468.

EF Core 6.0'ın öncesinde, yöntemlerin HasConversion genel aşırı yüklemeleri dönüştürülecek türü belirtmek için genel parametresini kullandı. Örneğin, bir Currency sabit listesi düşünün:

public enum Currency
{
    UsDollars,
    PoundsSterling,
    Euros
}

EF Core, kullanarak bu sabit listesi değerlerini "UsDollars", "PoundStirling" ve "Euro" HasConversion<string>dizeleri olarak kaydedecek şekilde yapılandırılabilir. Örnek:

modelBuilder.Entity<TestEntity1>()
    .Property(e => e.Currency)
    .HasConversion<string>();

EF Core 6.0'dan başlayarak, genel tür bunun yerine bir değer dönüştürücü türü belirtebilir. Bu, yerleşik değer dönüştürücülerinden biri olabilir. Örneğin, sabit listesi değerlerini veritabanında 16 bit sayılar olarak depolamak için:

modelBuilder.Entity<TestEntity2>()
    .Property(e => e.Currency)
    .HasConversion<EnumToNumberConverter<Currency, short>>();

Veya özel bir değer dönüştürücü türü olabilir. Örneğin, sabit listesi değerlerini para birimi simgeleri olarak depolayan bir dönüştürücü düşünün:

public class CurrencyToSymbolConverter : ValueConverter<Currency, string>
{
    public CurrencyToSymbolConverter()
        : base(
            v => v == Currency.PoundsSterling ? "£" : v == Currency.Euros ? "€" : "$",
            v => v == "£" ? Currency.PoundsSterling : v == "€" ? Currency.Euros : Currency.UsDollars)
    {
    }
}

Bu artık genel HasConversion yöntem kullanılarak yapılandırılabilir:

modelBuilder.Entity<TestEntity3>()
    .Property(e => e.Currency)
    .HasConversion<CurrencyToSymbolConverter>();

Çoka çok ilişkiler için daha az yapılandırma

GitHub Sorunu: #21535.

İki varlık türü arasındaki kesin olmayan çoka çok ilişkiler kural tarafından bulunur. Gerektiğinde veya istenirse gezintiler açıkça belirtilebilir. Örnek:

modelBuilder.Entity<Cat>()
    .HasMany(e => e.Humans)
    .WithMany(e => e.Cats);

Her iki durumda da EF Core, iki tür arasında birleştirme varlığı olarak davranması için temel Dictionary<string, object> alınan bir paylaşılan varlık oluşturur. EF Core 6.0'dan başlayarak, UsingEntity ek yapılandırmaya gerek kalmadan yalnızca bu türü değiştirmek için yapılandırmaya eklenebilir. Örnek:

modelBuilder.Entity<Cat>()
    .HasMany(e => e.Humans)
    .WithMany(e => e.Cats)
    .UsingEntity<CatHuman>();

Ayrıca, birleştirme varlık türü, sol ve sağ ilişkileri açıkça belirtmeye gerek kalmadan ek olarak yapılandırılabilir. Örnek:

modelBuilder.Entity<Cat>()
    .HasMany(e => e.Humans)
    .WithMany(e => e.Cats)
    .UsingEntity<CatHuman>(
        e => e.HasKey(e => new { e.CatsId, e.HumansId }));

Son olarak, tam yapılandırma sağlanabilir. Örnek:

modelBuilder.Entity<Cat>()
    .HasMany(e => e.Humans)
    .WithMany(e => e.Cats)
    .UsingEntity<CatHuman>(
        e => e.HasOne<Human>().WithMany().HasForeignKey(e => e.CatsId),
        e => e.HasOne<Cat>().WithMany().HasForeignKey(e => e.HumansId),
        e => e.HasKey(e => new { e.CatsId, e.HumansId }));

Değer dönüştürücülerinin null değerleri dönüştürmesine izin ver

GitHub Sorunu: #13850.

Önemli

Aşağıda açıklanan sorunlardan dolayı, NULL'ların dönüştürülmesi için ValueConverter izin veren oluşturucular EF Core 6.0 sürümü için ile [EntityFrameworkInternal] işaretlenmiştir. Bu oluşturucuları kullanmak artık bir derleme uyarısı oluşturur.

Değer dönüştürücüleri genellikle null değerinin başka bir değere dönüştürülebilmesine izin vermez. Bunun nedeni, aynı değer dönüştürücüsunun hem null atanabilir hem de boş değer atanamayan türler için kullanılabilmesidir. Bu, FK'nin genellikle null atanabilir olduğu ve PK'nın olmadığı PK/FK bileşimleri için çok yararlıdır.

EF Core 6.0'dan başlayarak, null değerleri dönüştüren bir değer dönüştürücü oluşturulabilir. Ancak bu özelliğin doğrulanması, birçok tuzakla pratikte çok sorunlu olduğunu ortaya çıkarmıştır. Örnek:

Bunlar önemsiz sorunlar değildir ve sorgu sorunları için algılanmaları kolay değildir. Bu nedenle, bu özelliği EF Core 6.0 için dahili olarak işaretledik. Yine de kullanabilirsiniz, ancak derleyici uyarısı alırsınız. Uyarı kullanılarak #pragma warning disable EF1001devre dışı bırakılabilir.

Null değerleri dönüştürmenin yararlı olabileceği durumlardan biri, veritabanı null değer içerdiğinde ancak varlık türü özelliği için başka bir varsayılan değer kullanmak istemesidir. Örneğin, varsayılan değerinin "Bilinmiyor" olduğu bir sabit listesi düşünün:

public enum Breed
{
    Unknown,
    Burmese,
    Tonkinese
}

Ancak, cins bilinmediğinde veritabanı null değerlere sahip olabilir. EF Core 6.0'da, bunu hesaba katan bir değer dönüştürücü kullanılabilir:

    public class BreedConverter : ValueConverter<Breed, string>
    {
#pragma warning disable EF1001
        public BreedConverter()
            : base(
                v => v == Breed.Unknown ? null : v.ToString(),
                v => v == null ? Breed.Unknown : Enum.Parse<Breed>(v),
                convertsNulls: true)
        {
        }
#pragma warning restore EF1001
    }

"Bilinmiyor" cinsine sahip Breed kedilerin sütunu veritabanında null olarak ayarlanır. Örnek:

context.AddRange(
    new Cat { Name = "Mac", Breed = Breed.Unknown },
    new Cat { Name = "Clippy", Breed = Breed.Burmese },
    new Cat { Name = "Sid", Breed = Breed.Tonkinese });

context.SaveChanges();

Sql Server'da aşağıdaki insert deyimlerini oluşturur:

info: 9/27/2021 19:43:55.966 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (16ms) [Parameters=[@p0=NULL (Size = 4000), @p1='Mac' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Cats] ([Breed], [Name])
      VALUES (@p0, @p1);
      SELECT [Id]
      FROM [Cats]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: 9/27/2021 19:43:55.983 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p0='Burmese' (Size = 4000), @p1='Clippy' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Cats] ([Breed], [Name])
      VALUES (@p0, @p1);
      SELECT [Id]
      FROM [Cats]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: 9/27/2021 19:43:55.983 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p0='Tonkinese' (Size = 4000), @p1='Sid' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Cats] ([Breed], [Name])
      VALUES (@p0, @p1);
      SELECT [Id]
      FROM [Cats]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

DbContext fabrika geliştirmeleri

AddDbContextFactory ayrıca DbContext'i doğrudan kaydeder

GitHub Sorunu: #25164.

Bazen hem DbContext türüne hem de uygulama bağımlılık ekleme (D.I.) kapsayıcısına kaydedilen bu tür bağlamlar için bir fabrikaya sahip olmak yararlı olabilir. Bu, örneğin DbContext'in kapsamlı bir örneğinin istek kapsamından çözümlenmesine olanak tanırken, fabrika gerektiğinde birden çok bağımsız örnek oluşturmak için kullanılabilir.

Bunu desteklemek için dbContext AddDbContextFactory türünü de kapsamlı bir hizmet olarak kaydeder. Örneğin, uygulamanın D.I. kapsayıcısında bu kaydı göz önünde bulundurun:

var container = services
    .AddDbContextFactory<SomeDbContext>(
        builder => builder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCoreSample;ConnectRetryCount=0"))
    .BuildServiceProvider();

Bu kayıtla fabrika, önceki sürümlerde olduğu gibi kök D.I. kapsayıcısından çözümlenebilir:

var factory = container.GetService<IDbContextFactory<SomeDbContext>>();
using (var context = factory.CreateDbContext())
{
    // Contexts obtained from the factory must be explicitly disposed
}

Fabrika tarafından oluşturulan bağlam örneklerinin açıkça atılması gerektiğini unutmayın.

Ayrıca, bir DbContext örneği doğrudan bir kapsayıcı kapsamından çözümlenebilir:

using (var scope = container.CreateScope())
{
    var context = scope.ServiceProvider.GetService<SomeDbContext>();
    // Context is disposed when the scope is disposed
}

Bu durumda kapsayıcı kapsamı atıldığında bağlam örneği atılır; bağlam açıkça atılmamalıdır.

Daha yüksek bir düzeyde bu, fabrikanın DbContext'inin diğer D.I. türlerine eklenebilir olduğu anlamına gelir. Örnek:

private class MyController2
{
    private readonly IDbContextFactory<SomeDbContext> _contextFactory;

    public MyController2(IDbContextFactory<SomeDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public void DoSomething()
    {
        using var context1 = _contextFactory.CreateDbContext();
        using var context2 = _contextFactory.CreateDbContext();

        var results1 = context1.Blogs.ToList();
        var results2 = context2.Blogs.ToList();

        // Contexts obtained from the factory must be explicitly disposed
    }
}

Veya:

private class MyController1
{
    private readonly SomeDbContext _context;

    public MyController1(SomeDbContext context)
    {
        _context = context;
    }

    public void DoSomething()
    {
        var results = _context.Blogs.ToList();

        // Injected context is disposed when the request scope is disposed
    }
}

DbContextFactory, DbContext parametresiz oluşturucuyu yoksayar

GitHub Sorunu: #24124.

EF Core 6.0 artık parametresiz bir DbContext oluşturucusunun ve fabrika aracılığıyla AddDbContextFactorykaydedildiğinde aynı bağlam türünde kullanılması gereken bir oluşturucuya DbContextOptions izin verir. Örneğin, yukarıdaki örneklerde kullanılan bağlam her iki oluşturucuyu da içerir:

public class SomeDbContext : DbContext
{
    public SomeDbContext()
    {
    }

    public SomeDbContext(DbContextOptions<SomeDbContext> options)
        : base(options)
    {
    }

    public DbSet<Blog> Blogs { get; set; }
}

DbContext havuzu bağımlılık eklemeden kullanılabilir

GitHub Sorunu: #24137.

Tür PooledDbContextFactory , uygulamanızın bağımlılık ekleme kapsayıcısına sahip olmasına gerek kalmadan DbContext örnekleri için tek başına havuz olarak kullanılabilmesi için genel kullanıma açık hale getirildi. Havuz, bağlam örnekleri oluşturmak için kullanılacak bir örneğiyle DbContextOptions oluşturulur:

var options = new DbContextOptionsBuilder<SomeDbContext>()
    .EnableSensitiveDataLogging()
    .UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=EFCoreSample;ConnectRetryCount=0")
    .Options;

var factory = new PooledDbContextFactory<SomeDbContext>(options);

Daha sonra fabrika, örnekleri oluşturmak ve havuza almak için kullanılabilir. Örnek:

for (var i = 0; i < 2; i++)
{
    using var context1 = factory.CreateDbContext();
    Console.WriteLine($"Created DbContext with ID {context1.ContextId}");

    using var context2 = factory.CreateDbContext();
    Console.WriteLine($"Created DbContext with ID {context2.ContextId}");
}

Örnekler atıldığında havuza döndürülür.

Çeşitli geliştirmeler

Son olarak EF Core, yukarıda ele alınmayan alanlarda çeşitli geliştirmeler içerir.

Tablo oluştururken [ColumnAttribute.Order] kullanma

GitHub Sorunu: #10059.

Order özelliği ColumnAttribute artık geçişlerle tablo oluştururken sütunları sıralamak için kullanılabilir. Örneğin, aşağıdaki modeli göz önünde bulundurun:

public class EntityBase
{
    public int Id { get; set; }
    public DateTime UpdatedOn { get; set; }
    public DateTime CreatedOn { get; set; }
}

public class PersonBase : EntityBase
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Employee : PersonBase
{
    public string Department { get; set; }
    public decimal AnnualSalary { get; set; }
    public Address Address { get; set; }
}

[Owned]
public class Address
{
    public string House { get; set; }
    public string Street { get; set; }
    public string City { get; set; }

    [Required]
    public string Postcode { get; set; }
}

Varsayılan olarak, EF Core birincil anahtar sütunlarını önce sıralar, varlık türünün ve sahip olunan türlerin özelliklerini ve son olarak temel türlerdeki özellikleri takip eder. Örneğin, SQL Server'da aşağıdaki tablo oluşturulur:

CREATE TABLE [EmployeesWithoutOrdering] (
    [Id] int NOT NULL IDENTITY,
    [Department] nvarchar(max) NULL,
    [AnnualSalary] decimal(18,2) NOT NULL,
    [Address_House] nvarchar(max) NULL,
    [Address_Street] nvarchar(max) NULL,
    [Address_City] nvarchar(max) NULL,
    [Address_Postcode] nvarchar(max) NULL,
    [UpdatedOn] datetime2 NOT NULL,
    [CreatedOn] datetime2 NOT NULL,
    [FirstName] nvarchar(max) NULL,
    [LastName] nvarchar(max) NULL,
    CONSTRAINT [PK_EmployeesWithoutOrdering] PRIMARY KEY ([Id]));

EF Core 6.0'da farklı ColumnAttribute bir sütun sırası belirtmek için kullanılabilir. Örnek:

public class EntityBase
{
    [Column(Order = 1)]
    public int Id { get; set; }

    [Column(Order = 98)]
    public DateTime UpdatedOn { get; set; }

    [Column(Order = 99)]
    public DateTime CreatedOn { get; set; }
}

public class PersonBase : EntityBase
{
    [Column(Order = 2)]
    public string FirstName { get; set; }

    [Column(Order = 3)]
    public string LastName { get; set; }
}

public class Employee : PersonBase
{
    [Column(Order = 20)]
    public string Department { get; set; }

    [Column(Order = 21)]
    public decimal AnnualSalary { get; set; }

    public Address Address { get; set; }
}

[Owned]
public class Address
{
    [Column("House", Order = 10)]
    public string House { get; set; }

    [Column("Street", Order = 11)]
    public string Street { get; set; }

    [Column("City", Order = 12)]
    public string City { get; set; }

    [Required]
    [Column("Postcode", Order = 13)]
    public string Postcode { get; set; }
}

SQL Server'da oluşturulan tablo şu şekildedir:

CREATE TABLE [EmployeesWithOrdering] (
    [Id] int NOT NULL IDENTITY,
    [FirstName] nvarchar(max) NULL,
    [LastName] nvarchar(max) NULL,
    [House] nvarchar(max) NULL,
    [Street] nvarchar(max) NULL,
    [City] nvarchar(max) NULL,
    [Postcode] nvarchar(max) NULL,
    [Department] nvarchar(max) NULL,
    [AnnualSalary] decimal(18,2) NOT NULL,
    [UpdatedOn] datetime2 NOT NULL,
    [CreatedOn] datetime2 NOT NULL,
    CONSTRAINT [PK_EmployeesWithOrdering] PRIMARY KEY ([Id]));

Bu, FistNameLastName ve sütunları bir temel tür içinde tanımlanmış olsalar bile en üste taşınır. Sütun sırası değerlerinin boşluklar olabileceğine dikkat edin ve birden çok türetilmiş tür tarafından kullanıldığında bile sütunları her zaman sonuna yerleştirmek için aralıkların kullanılmasına izin verin.

Bu örnek, aynı ColumnAttribute sütunun hem sütun adını hem de sırasını belirtmek için nasıl kullanılabileceğini de gösterir.

Sütun sıralama, içindeki OnModelCreatingAPI kullanılarak ModelBuilder da yapılandırılabilir. Örnek:

modelBuilder.Entity<UsingModelBuilder.Employee>(
    entityBuilder =>
    {
        entityBuilder.Property(e => e.Id).HasColumnOrder(1);
        entityBuilder.Property(e => e.FirstName).HasColumnOrder(2);
        entityBuilder.Property(e => e.LastName).HasColumnOrder(3);

        entityBuilder.OwnsOne(
            e => e.Address,
            ownedBuilder =>
            {
                ownedBuilder.Property(e => e.House).HasColumnName("House").HasColumnOrder(4);
                ownedBuilder.Property(e => e.Street).HasColumnName("Street").HasColumnOrder(5);
                ownedBuilder.Property(e => e.City).HasColumnName("City").HasColumnOrder(6);
                ownedBuilder.Property(e => e.Postcode).HasColumnName("Postcode").HasColumnOrder(7).IsRequired();
            });

        entityBuilder.Property(e => e.Department).HasColumnOrder(8);
        entityBuilder.Property(e => e.AnnualSalary).HasColumnOrder(9);
        entityBuilder.Property(e => e.UpdatedOn).HasColumnOrder(10);
        entityBuilder.Property(e => e.CreatedOn).HasColumnOrder(11);
    });

ile model oluşturucusunun sıralanması ile HasColumnOrderColumnAttributebelirtilen herhangi bir siparişe göre önceliklidir. Bu, HasColumnOrder farklı özelliklerdeki öznitelikler aynı sipariş numarasını belirttiğinde çakışmaları çözmek de dahil olmak üzere özniteliklerle yapılan sıralamayı geçersiz kılmak için kullanılabilir.

Önemli

Genel durumda çoğu veritabanının yalnızca tablo oluşturulduğunda sütunları sıralamayı desteklediğini unutmayın. Bu, sütun sırası özniteliğinin mevcut bir tablodaki sütunları yeniden sıralamak için kullanılamayacağı anlamına gelir. Bunun önemli bir istisnası, geçişlerin tablonun tamamını yeni sütun siparişleriyle yeniden oluşturacağı SQLite'tir.

EF Core Minimal API

GitHub Sorunu: #25192.

.NET Core 6.0, .NET uygulamalarında geleneksel olarak ihtiyaç duyulan çok sayıda ortak kodu kaldıran basitleştirilmiş "minimal API'ler" içeren güncelleştirilmiş şablonlar içerir.

EF Core 6.0, DbContext türünü kaydeden ve veritabanı sağlayıcısının yapılandırmasını tek bir satırda sağlayan yeni bir uzantı yöntemi içerir. Örnek:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSqlite<MyDbContext>("Data Source=mydatabase.db");
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSqlServer<MyDbContext>(@"Server=(localdb)\mssqllocaldb;Database=MyDatabase");
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCosmos<MyDbContext>(
    "https://localhost:8081",
    "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");

Bunlar tam olarak şunlarla eşdeğerdir:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(
    options => options.UseSqlite("Data Source=mydatabase.db"));
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(
    options => options.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=MyDatabase;ConnectRetryCount=0"));
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(
    options => options.UseCosmos(
        "https://localhost:8081",
        "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="));

Dekont

EF Core minimal API'leri, bir DbContext ve sağlayıcının yalnızca çok temel kaydını ve yapılandırmasını destekler. EF Core'da bulunan tüm kayıt ve yapılandırma türlerine erişmek için , AddDbContextPool, AddDbContextFactoryvb. kullanınAddDbContext.

En düşük API'ler hakkında daha fazla bilgi edinmek için şu kaynaklara göz atın:

SaveChangesAsync'te eşitleme bağlamı koruma

GitHub Sorunu: #23971.

5.0 sürümünde EF Core kodunu zaman uyumsuz kod yaptığımız tüm yerlerde olarak ayarlandı olarak Task.ConfigureAwaitfalse değiştirdikawait. Bu genellikle EF Core kullanımı için daha iyi bir seçimdir. Ancak EF Core, SaveChangesAsync zaman uyumsuz veritabanı işlemi tamamlandıktan sonra izlenen varlıklara oluşturulan değerleri ayarlayacağından özel bir durumdur. Bu değişiklikler daha sonra, örneğin U.I. iş parçacığında çalıştırılması gerekebilecek bildirimleri tetikleyebilir. Bu nedenle, bu değişikliği yalnızca yöntemi için SaveChangesAsync EF Core 6.0'da geri döndürüyoruz.

Bellek içi veritabanı: Gerekli özelliklerin null olmadığını doğrulayın

GitHub Sorunu: #10613. Bu özellik @fagnercarvalho tarafından katkıda bulundu. Çok teşekkürler!

Ef Core bellek içi veritabanı artık gerekli olarak işaretlenmiş bir özellik için null değer kaydetmeye çalışılırsa bir özel durum oluşturur. Örneğin, gerekli Username özelliğe sahip bir User tür düşünün:

public class User
{
    public int Id { get; set; }

    [Required]
    public string Username { get; set; }
}

Bir varlığı null Username ile kaydetmeye çalışmak aşağıdaki özel durumla sonuçlanır:

Microsoft.EntityFrameworkCore.DbUpdateException: '{'Username'}', '{Id: 1}' anahtar değerine sahip 'User' varlık türünün örneği için eksik.

Gerekirse bu doğrulama devre dışı bırakılabilir. Örnek:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .LogTo(Console.WriteLine, new[] { InMemoryEventId.ChangesSaved })
        .UseInMemoryDatabase("UserContextWithNullCheckingDisabled", b => b.EnableNullChecks(false));
}

Tanılama ve kesme makineleri için komut kaynağı bilgileri

GitHub Sorunu: #23719. Bu özellik @Giorgi tarafından katkıda bulundu. Çok teşekkürler!

CommandEventData Tanılama kaynaklarına ve kesme makinelerine sağlanan artık komutu oluşturmak için EF'nin hangi bölümünün sorumlu olduğunu belirten bir numaralandırma değeri içeriyor. Bu, tanılama veya kesme aracında filtre olarak kullanılabilir. Örneğin, yalnızca içinden SaveChangesgelen komutlar için geçerli olan bir kesme noktası isteyebiliriz:

public class CommandSourceInterceptor : DbCommandInterceptor
{
    public override InterceptionResult<DbDataReader> ReaderExecuting(
        DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
    {
        if (eventData.CommandSource == CommandSource.SaveChanges)
        {
            Console.WriteLine($"Saving changes for {eventData.Context!.GetType().Name}:");
            Console.WriteLine();
            Console.WriteLine(command.CommandText);
        }

        return result;
    }
}

Bu, kesme noktasını yalnızca SaveChanges geçişler ve sorgular da oluşturan bir uygulamada kullanıldığında gerçekleşen olaylara göre filtreler. Örnek:

Saving changes for CustomersContext:

SET NOCOUNT ON;
INSERT INTO [Customers] ([Name])
VALUES (@p0);
SELECT [Id]
FROM [Customers]
WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

Daha iyi geçici değerleri işleme

GitHub Sorunu: #24245.

EF Core, varlık türü örneklerinde geçici değerleri kullanıma sunmaz. Örneğin, depo tarafından oluşturulan anahtara sahip bir varlık türünü göz önünde bulundurun Blog :

public class Blog
{
    public int Id { get; set; }

    public ICollection<Post> Posts { get; } = new List<Post>();
}

Id Anahtar özelliği, bağlam tarafından izlenir izlenmez geçici bir Blog değer alır. Örneğin, çağrılırken DbContext.Add:

var blog = new Blog();
context.Add(blog);

Geçici değer, bağlam değişikliği izleyicisinden alınabilir, ancak varlık örneğine ayarlanmamıştır. Örneğin, bu kod:

Console.WriteLine($"Blog.Id value on entity instance = {blog.Id}");
Console.WriteLine($"Blog.Id value tracked by EF = {context.Entry(blog).Property(e => e.Id).CurrentValue}");

Aşağıdaki çıkışı oluşturur:

Blog.Id value on entity instance = 0
Blog.Id value tracked by EF = -2147482647

Bu, uygulama koduna geçici değerin sızmasını önlediği ve yanlışlıkla geçici olmayan olarak ele alınabileceği için iyidir. Ancak, bazen geçici değerlerle doğrudan ilgilenmek yararlı olabilir. Örneğin, bir uygulama izlenmeden önce varlıkların grafiği için kendi geçici değerlerini oluşturmak isteyebilir, böylece yabancı anahtarları kullanarak ilişki oluşturmak için kullanılabilir. Bu, değerleri açıkça geçici olarak işaretleyerek yapılabilir. Örnek:

var blog = new Blog { Id = -1 };
var post1 = new Post { Id = -1, BlogId = -1 };
var post2 = new Post { Id = -2, BlogId = -1 };

context.Add(blog).Property(e => e.Id).IsTemporary = true;
context.Add(post1).Property(e => e.Id).IsTemporary = true;
context.Add(post2).Property(e => e.Id).IsTemporary = true;

Console.WriteLine($"Blog has explicit temporary ID = {blog.Id}");
Console.WriteLine($"Post 1 has explicit temporary ID = {post1.Id} and FK to Blog = {post1.BlogId}");
Console.WriteLine($"Post 2 has explicit temporary ID = {post2.Id} and FK to Blog = {post2.BlogId}");

EF Core 6.0'da, değer artık geçici olarak işaretlenmiş olsa bile varlık örneğinde kalır. Örneğin, yukarıdaki kod aşağıdaki çıkışı oluşturur:

Blog has explicit temporary ID = -1
Post 1 has explicit temporary ID = -1 and FK to Blog = -1
Post 2 has explicit temporary ID = -2 and FK to Blog = -1

Benzer şekilde, EF Core tarafından oluşturulan geçici değerler varlık örneklerine açıkça ayarlanabilir ve geçici değerler olarak işaretlenebilir. Bu, geçici anahtar değerlerini kullanarak yeni varlıklar arasındaki ilişkileri açıkça ayarlamak için kullanılabilir. Örnek:

var post1 = new Post();
var post2 = new Post();

var blogIdEntry = context.Entry(blog).Property(e => e.Id);
blog.Id = blogIdEntry.CurrentValue;
blogIdEntry.IsTemporary = true;

var post1IdEntry = context.Add(post1).Property(e => e.Id);
post1.Id = post1IdEntry.CurrentValue;
post1IdEntry.IsTemporary = true;
post1.BlogId = blog.Id;

var post2IdEntry = context.Add(post2).Property(e => e.Id);
post2.Id = post2IdEntry.CurrentValue;
post2IdEntry.IsTemporary = true;
post2.BlogId = blog.Id;

Console.WriteLine($"Blog has generated temporary ID = {blog.Id}");
Console.WriteLine($"Post 1 has generated temporary ID = {post1.Id} and FK to Blog = {post1.BlogId}");
Console.WriteLine($"Post 2 has generated temporary ID = {post2.Id} and FK to Blog = {post2.BlogId}");

Sonuç olarak:

Blog has generated temporary ID = -2147482647
Post 1 has generated temporary ID = -2147482647 and FK to Blog = -2147482647
Post 2 has generated temporary ID = -2147482646 and FK to Blog = -2147482647

C# null atanabilir başvuru türleri için EF Core açıklamalı

GitHub Sorunu: #19007.

EF Core kod tabanı artık C# null atanabilir başvuru türlerini (NRTs) kullanır. Bu, kendi kodunuzdan EF Core 6.0 kullanırken null kullanımı için doğru derleyici göstergelerini alacağınız anlamına gelir.

Microsoft.Data.Sqlite 6.0

Bahşiş

GitHub'dan örnek kodu indirerek aşağıda gösterilen tüm örneklerde komutunu çalıştırabilir ve hata ayıklayabilirsiniz.

Bağlantı Havuzu

GitHub Sorunu: #13837.

Veritabanı bağlantılarını mümkün olduğunca kısa bir süre açık tutmak yaygın bir uygulamadır. Bu, bağlantı kaynağında çakışmayı önlemeye yardımcı olur. Bu nedenle EF Core gibi kitaplıklar bir veritabanı işlemi gerçekleştirmeden hemen önce bağlantıyı açar ve hemen sonra yeniden kapatır. Örneğin, şu EF Core kodunu göz önünde bulundurun:

Console.WriteLine("Starting query...");
Console.WriteLine();

var users = context.Users.ToList();

Console.WriteLine();
Console.WriteLine("Query finished.");
Console.WriteLine();

foreach (var user in users)
{
    if (user.Username.Contains("microsoft"))
    {
        user.Username = "msft:" + user.Username;

        Console.WriteLine("Starting SaveChanges...");
        Console.WriteLine();

        context.SaveChanges();

        Console.WriteLine();
        Console.WriteLine("SaveChanges finished.");
    }
}

Bu koddan alınan ve bağlantıların günlüğe kaydedilmesi açık olan çıkış şu şekildedir:

Starting query...

dbug: 8/27/2021 09:26:57.810 RelationalEventId.ConnectionOpened[20001] (Microsoft.EntityFrameworkCore.Database.Connection)
      Opened connection to database 'main' on server 'C:\dotnet\efdocs\samples\core\Miscellaneous\NewInEFCore6\bin\Debug\net6.0\test.db'.
dbug: 8/27/2021 09:26:57.813 RelationalEventId.ConnectionClosed[20003] (Microsoft.EntityFrameworkCore.Database.Connection)
      Closed connection to database 'main' on server 'test.db'.

Query finished.

Starting SaveChanges...

dbug: 8/27/2021 09:26:57.813 RelationalEventId.ConnectionOpened[20001] (Microsoft.EntityFrameworkCore.Database.Connection)
      Opened connection to database 'main' on server 'C:\dotnet\efdocs\samples\core\Miscellaneous\NewInEFCore6\bin\Debug\net6.0\test.db'.
dbug: 8/27/2021 09:26:57.814 RelationalEventId.ConnectionClosed[20003] (Microsoft.EntityFrameworkCore.Database.Connection)
      Closed connection to database 'main' on server 'test.db'.

SaveChanges finished.

Bağlantının her işlem için hızlı bir şekilde açıldığına ve kapatıldığına dikkat edin.

Ancak çoğu veritabanı sistemi için veritabanına fiziksel bir bağlantı açmak pahalı bir işlemdir. Bu nedenle çoğu ADO.NET sağlayıcısı bir fiziksel bağlantı havuzu oluşturur ve gerektiğinde bunları örneklere DbConnection kiralar.

Veritabanı erişimi genellikle yalnızca bir dosyaya eriştiğinden SQLite biraz farklıdır. Bu, SQLite veritabanına bağlantı açmanın genellikle çok hızlı olduğu anlamına gelir. Ancak, her zaman böyle değildir. Örneğin, şifrelenmiş bir veritabanına bağlantı açmak çok yavaş olabilir. Bu nedenle, SQLite bağlantıları artık Microsoft.Data.Sqlite 6.0 kullanılırken havuza alınıyor.

DateOnly ve TimeOnly Desteği

GitHub Sorunu: #24506.

Microsoft.Data.Sqlite 6.0, .NET 6'dan yeni DateOnly ve TimeOnly türleri destekler. Bunlar SQLite sağlayıcısıyla EF Core 6.0'da da kullanılabilir. Her zaman SQLite'de olduğu gibi yerel tür sistemi, bu türlerdeki değerlerin desteklenen dört türden biri olarak depolanması gerektiği anlamına gelir. Microsoft.Data.Sqlite bunları olarak TEXTdepolar. Örneğin, şu türleri kullanan bir varlık:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    
    public DateOnly Birthday { get; set; }
    public TimeOnly TokensRenewed { get; set; }
}

SQLite veritabanında aşağıdaki tabloya Haritalar:

CREATE TABLE "Users" (
    "Id" INTEGER NOT NULL CONSTRAINT "PK_Users" PRIMARY KEY AUTOINCREMENT,
    "Username" TEXT NULL,
    "Birthday" TEXT NOT NULL,
    "TokensRenewed" TEXT NOT NULL);

Değerler daha sonra normal şekilde kaydedilebilir, sorgulanabilir ve güncelleştirilebilir. Örneğin, bu EF Core LINQ sorgusu:

var users = context.Users.Where(u => u.Birthday < new DateOnly(1900, 1, 1)).ToList();

SQLite'te aşağıdakine çevrilir:

SELECT "u"."Id", "u"."Birthday", "u"."TokensRenewed", "u"."Username"
FROM "Users" AS "u"
WHERE "u"."Birthday" < '1900-01-01'

Ve yalnızca 1900 CE öncesi doğum günleriyle kullanımları döndürür:

Found 'ajcvickers'
Found 'wendy'

Kayıt Noktaları API'si

GitHub Sorunu: #20228.

ADO.NET sağlayıcılarındaki kayıt noktaları için ortak bir API'yi standartlaştırdık. Microsoft.Data.Sqlite artık aşağıdakiler dahil olmak üzere bu API'yi destekliyor:

Bir kayıt noktası kullanmak, işlemin tamamını geri almadan işlemin bir kısmının geri alınmasına olanak tanır. Örneğin, aşağıdaki kod:

  • İşlem oluşturur
  • Veritabanına güncelleştirme gönderir
  • Bir kaydetme noktası oluşturur
  • Veritabanına başka bir güncelleştirme gönderir
  • Daha önce oluşturulan kayıt noktasına geri döner
  • İşlemi işler
using var connection = new SqliteConnection("Command Timeout=60;DataSource=test.db");
connection.Open();

using var transaction = connection.BeginTransaction();

using (var command = connection.CreateCommand())
{
    command.CommandText = @"UPDATE Users SET Username = 'ajcvickers' WHERE Id = 1";
    command.ExecuteNonQuery();
}

transaction.Save("MySavepoint");

using (var command = connection.CreateCommand())
{
    command.CommandText = @"UPDATE Users SET Username = 'wfvickers' WHERE Id = 2";
    command.ExecuteNonQuery();
}

transaction.Rollback("MySavepoint");

transaction.Commit();

Bu, ilk güncelleştirmenin veritabanına işlenmesine neden olurken, kayıt noktası işlem yürütülmeden önce geri alındığından ikinci güncelleştirme işlenmez.

bağlantı dizesi komut zaman aşımı

GitHub Sorunu: #22505. Bu özellik @nmichels tarafından katkıda bulundu. Çok teşekkürler!

ADO.NET sağlayıcıları iki ayrı zaman aşımını destekler:

  • Veritabanına bağlantı yapılırken bekleyebileceğiniz en uzun süreyi belirleyen bağlantı zaman aşımı.
  • Komutun yürütülmesinin tamamlanması için beklenmesi gereken en uzun süreyi belirleyen komut zaman aşımı.

Komut zaman aşımı kullanılarak koddan DbCommand.CommandTimeoutayarlanabilir. Artık birçok sağlayıcı da bu komut zaman aşımını bağlantı dizesi ortaya çıkartıyor. Microsoft.Data.Sqlite, bağlantı dizesi anahtar sözcüğüyle Command Timeout bu eğilimi takip ediyor. Örneğin, "Command Timeout=60;DataSource=test.db" bağlantı tarafından oluşturulan komutlar için varsayılan zaman aşımı olarak 60 saniye kullanır.

Bahşiş

Sqlite, için bir eş anlamlı Command Timeout olarak ele Default Timeout alır ve tercih edilirse bunun yerine kullanılabilir.