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 PeriodStart
adlı 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
});
await context.SaveChangesAsync();
Bu veriler daha sonra normal şekilde sorgulanabilir, güncelleştirilebilir ve silinebilir. Örnek:
var employee = await context.Employees.SingleAsync(e => e.Name == "Rainbow Dash");
context.Remove(employee);
await context.SaveChangesAsync();
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 = await context.Employees.ToListAsync();
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ırTemporalFromTo
.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.
Dekont
Bu işleçlerin her biri için tam olarak hangi satırların dahil olduğu hakkında daha fazla bilgi için SQL Server zamana bağlı tablolar belgelerine bakın.
Ö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 = await 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")
})
.ToListAsync();
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 TemporalBetween
TemporalContainedIn
kullanılarak TemporalFromTo
yazılabilir. Örnek:
var history = await 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")
})
.ToListAsync();
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 = await context
.Employees
.TemporalAsOf(timeStamp2)
.SingleAsync(e => e.Name == "Rainbow Dash");
context.Add(employee);
await context.SaveChangesAsync();
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ı SaveChanges
yapmamı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.
Dekont
Geçişler, paketler ve dağıtım hakkında daha ayrıntılı bir tartışma için .NET Blogunda DevOps dostu EF Core Geçiş Paketlerine Giriş bölümüne bakın.
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 bundle
bir 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 add
daha 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 DbContext
geç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:
- Genel sorgu filtreleri desteklenmez.
- Gecikmeli yükleme ve değişiklik izleme proxy'leri desteklenmez.
- Özel IModelCacheKeyFactory uygulamaları desteklenmez. Ancak, birden çok modeli derleyebilir ve gerektiği gibi uygun olanı yükleyebilirsiniz.
- Model tanımı veya yapılandırma değiştiğinde model el ile yeniden oluşturularak eşitlenmelidir.
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.
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 |
STRINGEQUALS |
Yalnızca büyük/küçük harfe duyarlı olmayan çağrılar |
, , LTRIM
, RTRIM
, TRIM
, ve UPPER
SUBSTRING
ç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 Tags
bunları 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 = await context.People
.Include(e => e.Shoes)
.GroupBy(e => e.FirstName)
.Select(
g => g.OrderBy(e => e.FirstName)
.ThenBy(e => e.LastName)
.FirstOrDefault())
.ToListAsync();
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 = await context.People
.Select(
p => new
{
p.FirstName,
FullName = p.FirstName + " " + p.MiddleInitial + " " + p.LastName
})
.GroupBy(p => p.FirstName)
.Select(g => g.First())
.FirstAsync();
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 = await context.People
.Where(e => e.MiddleInitial == "Q" && e.Age == 20)
.GroupBy(e => e.LastName)
.Select(g => g.First().LastName)
.OrderBy(e => e.Length)
.ToListAsync();
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 = await (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()
})
.ToListAsync();
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 = await context.People
.GroupBy(e => e.FirstName)
.Select(g => g.First().LastName)
.OrderBy(e => e)
.ToListAsync();
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 = await context.People
.Where(e => e.Age == 20)
.GroupBy(e => e.Id)
.Select(g => g.First().MiddleInitial)
.OrderBy(e => e)
.ToListAsync();
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
= await 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),
})
.ToListAsync();
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 = await 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)
})
.CountAsync();
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 = await context.People
.GroupBy(n => n.FirstName)
.Select(g => new
{
Feet = g.Key,
Total = g.Sum(n => n.Feet.Size)
})
.ToListAsync();
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 = await 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)
.ToListAsync();
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 = await 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()})
.ToListAsync();
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 = await context.People
.GroupBy(m => new {m.FirstName, m.MiddleInitial })
.Select(am => new
{
Key = am.Key,
Items = am.ToList()
})
.ToListAsync();
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 = await context.Shards
.Where(e => string.Concat(e.Token1, e.Token2, e.Token3) != e.TokensProcessed).ToListAsync();
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.
Daha esnek SQL Server serbest metin araması
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 string
bile yürütülebilir. Örnek:
var result = await context.Customers.Where(e => EF.Functions.Contains(e.Name, "Martin")).ToListAsync();
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 = await context.Users.Where(u => EF.Functions.Like(u.PhoneNumber.ToString(), "%555%")).ToListAsync();
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 = await context.Users.Where(u => u.Popularity == (int)(EF.Functions.Random() * 4.0) + 1).ToListAsync();
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 = await context.Users.Where(
e => string.IsNullOrWhiteSpace(e.FirstName)
|| string.IsNullOrWhiteSpace(e.LastName)).ToListAsync();
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, OnModelCreating
için CustomerDensities
dö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 = await context.CustomerDensities.ToListAsync();
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 = await context.Customers
.Select(a => new { Name = a.Name.Substring(3) })
.FirstOrDefaultAsync(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:
await context.Customers
.Select(
c => new
{
c,
Orders = c.Orders
.Where(o => o.Id > 1)
})
.ToListAsync();
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:
await context.Customers
.Select(
c => new
{
c,
OrderDates = c.Orders
.Where(o => o.Id > 1)
.Select(o => o.OrderDate)
})
.ToListAsync();
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:
await context.Customers
.Select(
c => new
{
c,
OrderDates = c.Orders
.Where(o => o.Id > 1)
.Select(o => o.OrderDate)
.Distinct()
})
.ToListAsync();
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:
await context.Customers
.Select(
e => new
{
e.Id,
FirstOrder = e.Orders.Where(i => i.Id == 1).ToList()
})
.ToListAsync();
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 = await context
.Customers
.TagWithCallSite()
.Where(c => c.Name.StartsWith("A"))
.ToListAsync();
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 Address
olduğ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 Required
iş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:
await foreach (var customer in context.Customers1.AsAsyncEnumerable())
{
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 Address
ContactInfo
sahip olduğu ContactInfo
Customer
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:
- 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.
- Yukarıda açıklandığı gibi, bağımlının en az bir gerekli özellik içerdiğinden emin olun.
- İ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 OnModelCreating
kullanılacak tablolar belirtilerek farklı bir tabloya kaydedilebilir:
modelBuilder
.Entity<WithDifferentTable.Customer>(
b =>
{
b.ToTable("Customers");
b.OwnsOne(
e => e.Address,
b => b.ToTable("CustomerAddresses"));
});
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 ForumUser
geniş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:
- Depoda null değerine değer dönüştürme hatalı sorgular oluşturur
- Depodaki null değer dönüştürme hatalı sorgular oluşturur
- Değer dönüştürücüleri, veritabanı sütununun aynı değere dönüştüren birden çok farklı değere sahip olduğu durumları işlemez
- Değer dönüştürücülerinin sütunların null atanabilirliğini değiştirmesine izin ver
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 EF1001
devre 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 });
await context.SaveChangesAsync();
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 async Task DoSomething()
{
using var context1 = _contextFactory.CreateDbContext();
using var context2 = _contextFactory.CreateDbContext();
var results1 = await context1.Blogs.ToListAsync();
var results2 = await context2.Blogs.ToListAsync();
// Contexts obtained from the factory must be explicitly disposed
}
}
Veya:
private class MyController1
{
private readonly SomeDbContext _context;
public MyController1(SomeDbContext context)
{
_context = context;
}
public async Task DoSomething()
{
var results = await _context.Blogs.ToListAsync();
// 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 AddDbContextFactory
kaydedildiğ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, FistName
LastName
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 OnModelCreating
API 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 HasColumnOrder
ColumnAttribute
belirtilen 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
, AddDbContextFactory
vb. kullanınAddDbContext
.
En düşük API'ler hakkında daha fazla bilgi edinmek için şu kaynaklara göz atın:
- Maria Naggaga tarafından oluşturulan .NET 6'da minimal API'ler
- Scott Hanselman'ın blogundaki .NET 6 Minimal API Todo örneği Playground
- David Fowler tarafından bir bakışta gist Minimal API'leri
- GitHub'da Damian Edwards tarafından minimal BIR API Playground
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.ConfigureAwait false
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 SaveChanges
gelen 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 = await context.Users.ToListAsync();
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();
await context.SaveChangesAsync();
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 TEXT
depolar. Ö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 = await context.Users.Where(u => u.Birthday < new DateOnly(1900, 1, 1)).ToListAsync();
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:
- Save(String) işlemde bir kayıt noktası oluşturmak için
- Rollback(String) önceki bir kayıt noktasına geri dönmek için
- Release(String) bir kayıt noktasını serbest bırakmak için
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");
await connection.OpenAsync();
await using var transaction = await connection.BeginTransactionAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = @"UPDATE Users SET Username = 'ajcvickers' WHERE Id = 1";
await command.ExecuteNonQueryAsync();
}
await transaction.SaveAsync("MySavepoint");
using (var command = connection.CreateCommand())
{
command.CommandText = @"UPDATE Users SET Username = 'wfvickers' WHERE Id = 2";
await command.ExecuteNonQueryAsync();
}
await transaction.RollbackAsync("MySavepoint");
await transaction.CommitAsync();
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.