EF Core 7.0 κ³Ό EF Core 8.0 μμλ κΈ°μ‘΄ λ²μ μ EF λλ RDBμ νκ³λ‘ μΈν΄ λ€λ£¨κΈ° νλ€μλ νμλ€μ λν μ§μμ΄ λν μΆκ°λμμ΅λλ€.
μ΄ μ§μμ΄ νμν μ΄μ μ μ¬μ© λ°©λ²μ λν΄ μμ λ΄ λλ€.
Linq Filtering & Projection
μ°μ , EF μμ μ§μνλ€λ μλ―Έλ IQueryable Linq μμ νν°λ§(Where) κ³Ό ν¬μ¬(Projection, Select)κ° κ°λ₯νλ€λ μλ―ΈλΌλ μ μ μμ λμλ κ² μ’μ΅λλ€.
μ μ₯ κ°μ²΄
λ°μ΄ν° λ² μ΄μ€μ μ μ₯λ κ°μ²΄λ λμ²΄λ‘ μλ μΈ κ°μ§ ν° λΆλ₯λ‘ λλ μ μμ΅λλ€.
-
κΈ°λ³Έ μλ£ν(Primitive Types)
νλμ μ§ν©μΌλ‘ ννλμ§ μκ³ , λ¨μΌ κ°μ λνλ. (int, char, text β¦). -
μν°ν°(Entity Types)
νλμ μ§ν©μΌλ‘ ννλκ³ Key κ° μμ. -
λ³΅ν© μλ£ν(Complex Value Types)
νλμ μ§ν©μΌλ‘ ννλκ³ Key κ° μμ. ???
1λ²μ μ½λ λͺ¨λΈμ μμ± β λ μ½λλ‘, 2λ²μ μ½λ λͺ¨λΈ(ν΄λμ€) β ν μ΄λΈλ‘ 맡νμ΄ κ°λ₯ν©λλ€.
κ·Έλ¬λ, 3λ²μ κ²½μ° μ½λ λͺ¨λΈ μ μ₯μμλ λ¬Έμ κ° μμ§λ§, Keyλ₯Ό μ μ νλ RDBμ μ μ₯μμλ μ²λ¦¬ν λ°©λ²μ΄ μμ΅λλ€. μ¦, μ½λ λͺ¨λΈ β λ°μ΄ν° λ² μ΄μ€ 맡νμ κ΄ν μμ ν λ°©λ²μ΄ μ‘΄μ¬νμ§ μμ΅λλ€.
λ³΅ν© μλ£ν(Complex Types)
EF 7 μ΄μ μλ λ³΅ν© νμμ μ°ν 맡ννλ λ°©λ²μ ν¬κ² μΈ κ°μ§μμ΅λλ€.
- Owned Type μΌλ‘ μ μΈ
- Ownerμ ν
μ΄λΈμ 곡μ
λ³΅ν© μλ£νμ μμ±μ΄ Owner ν μ΄λΈμ νλλ‘ μ½μ λ¨ - 묡μμ ν€λ₯Ό 보μ ν ν
μ΄λΈλ‘ μ μΈ
Owner μ ν€λ₯Ό μΈλν€λ‘ 보μ ν ν μ΄λΈμ μ μ. μ΄ ν μ΄λΈμ Keyλ λ³λλ‘ μ μ.
- Ownerμ ν
μ΄λΈμ 곡μ
- μ§λ ¬νλ₯Ό ν΅ν΄ κΈ°λ³Έ μλ£νμΌλ‘ λ³ν
Owned Type μΌλ‘ μ μΈνλ κ²μ μ΄λ―Έ Keyλ₯Ό μ μ νλ Entity μ΄κΈ°μ key κ° μλ λ³΅ν© μλ£νκ³Ό μλ―Έλ‘ μ μΌλ‘ λΆν©νμ§ μκ³ , Keyλ₯Ό μΈμμ μΌλ‘ μ μν΄μΌ ν΄μ, μλ¬ μ λ° μμκ° μμ΅λλ€.
μ§λ ¬νμλ μλ λ κ°μ§ λ°©λ²μ΄ μμ΅λλ€.
- CSV (Comma Seperated Values)
- Json
μ§λ ¬νμ κ°μ₯ ν° λ¬Έμ μ μ Linq κ° λ¨Ήμ§ μλλ€λ μ μ
λλ€.
(PostgreSQL μ "Json νμ"μ μ§μνκΈ° λλ¬Έμ μμΈμ
λλ€)
μ΄λ¬ν λ³΅ν© νμμ 맡ν μ΄λ €μμ, EF 7μ λ€μ΄μ, "Json Column"μ΄λΌλ κΈ°λ₯μ λμ νμ¬ ν΄κ²°νκ³ , EF 8 μ "Complex Type"μ΄λΌλ μ΄λ¦μΌλ‘ λ€λ₯Έ μ κ·Όλ²μ μ μν©λλ€.
Json νμ νλ(Json Column)
λ°μ΄ν° λ² μ΄μ€κ° λ¬Έμμ΄ ν νλμ μ μ₯λ Json textλ₯Ό Json κ°μ²΄λ‘ μ·¨κΈνλ κΈ°λ₯μ κ°λ¦¬ν€λλ°, λλΆλΆμ RDBλ μ΄μ λν μ§μμ νκ³ μμ΄, 쿼리μ λμ(From)μ΄ λ μ μμ΅λλ€.
μ΄ κΈ°λ₯μ NoSQL κ°μ document λ°μ΄ν° λ² μ΄μ€μ νΉμ§μ RDBμ μ ν κ²μ΄λΌ ν μ μλλ°, μμ₯ λ°©μ΄μ μ°¨μμμ κΌ νμν λΆλΆμ΄μλ κ² κ°μ΅λλ€.
EF λ λ²μ 7λΆν°, "Json Column"μ λν μ§μμ μΆκ°νλλ°, Ownedλ‘ μ§μ λλ λ³΅ν© νμμ Json ColumnμΌλ‘ μ§μ νλ μ΅μ μ μ 곡νλ κ²μ΄ μ£Όμ 골μμ λλ€.
Fluent Api
μ°μ , EF 7μμλ Fluent API λ°©μμΌλ‘ μ€μ ν μ μκ² λμμ΅λλ€.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>().OwnsOne(
author => author.Contact, ownedNavigationBuilder =>
{
ownedNavigationBuilder.ToJson();
ownedNavigationBuilder.OwnsOne(contactDetails => contactDetails.Address);
});
}
μ μ½λμμ Authorλ Entityμ λλ€.
Authorλ Contact λ₯Ό μμ νκ³ , Contact λ λ€μ Addressλ₯Ό μμ ν©λλ€.
Author ν μ΄λΈμ Contact λ₯Ό Json Column μΌλ‘ μ§μ νκΈ° μν΄μ, Contact λΉλμ ToJson() λ©μλλ₯Ό νΈμΆν©λλ€. κ·Έ κ²°κ³Όλ‘ Contact κ°μ²΄λ [Author].[Contact] νλμ Json λ¬Έμμ΄λ‘ μ μ₯λ©λλ€.
κ·Έλ°λ°, Address λΉλμμλ μ΄λ₯Ό νΈμΆνμ§ μμ΅λλ€. μλνλ©΄ Address μ OwnerμΈ Contact κ° μ΄λ―Έ Json μΌλ‘ μ§μ λμκΈ° λλ¬Έμ, Address λ λΉμ°ν JsonμΌλ‘ μ§μ λκΈ° λλ¬Έμ λλ€.
Owns & Aggregate
EFμ Owns κ΄κ³λ μλ μ 그리κ²(Aggregate)μ λνλ΄κΈ° μν κ²μ΄μμ΅λλ€.
μ¦, Owner λ Aggregate Root, Owned λ Aggregate μ Sub Entity λ₯Ό κ°λ¦¬ν€λλ°, μ΄ κ°λ μ λ§κ² EFλ μΌμ ν κ°μ μ¬νμ λΆμ¬ν©λλ€.
- Sub Entity λ DbSet<T>μ λμμΌλ‘ μ¬μ©ν μ μμ΅λλ€.
Owned λ₯Ό λ€λ₯Έ Aggregateμμ μ°Έμ‘°νλ €λ©΄, λ°λμ DbSet<TOwner>μ ν΅νλ κ²μ΄ κ°μ λ©λλ€.- Ownedλ μΈμ λ Onwerμ Include λκ³ , Cacade Delete λ©λλ€.
Json Column μ μ§μ μ Aggregate Root μν°ν°μμλ§ κ°λ₯ν©λλ€.
μ‘°ν(Query)
μ΄λ κ² Json νλλ‘ μ§μ λ λ³΅ν© νμμ λν΄ μλμ κ°μ Linq κ° κ°λ₯ν©λλ€.
νν°λ§
var authorsInChigley = await context.Authors
.Where(author => author.Contact.Address.City == "Chigley")
.ToListAsync();
λ¨μ ν¬μ¬
var postcodesInChigley = await context.Authors
.Where(author => author.Contact.Address.City == "Chigley")
.Select(author => author.Contact.Address.Postcode)
.ToListAsync();
μ‘°ν© ν¬μ¬
var orderedAddresses = await context.Authors
.Where(
author => (author.Contact.Address.City == "Chigley"
&& author.Contact.Phone != null)
|| author.Name.StartsWith("D"))
.OrderBy(author => author.Contact.Phone)
.Select(
author => author.Name + " (" + author.Contact.Address.Street
+ ", " + author.Contact.Address.City
+ " " + author.Contact.Address.Postcode + ")")
.ToListAsync();
μ§ν©μ ν¬μ¬
var postsWithViews = await context.Posts
.Where(post => post.Metadata!.Views > 3000)
.AsNoTracking()
.Select(
post => new
{
post.Author!.Name, post.Metadata!.Views, Searches = post.Metadata.TopSearches, Commits = post.Metadata.Updates
})
.ToListAsync();
Update
DbContext.SaveChanges λ μΌλ° Entity μ²λΌ λ€λ£° μ μλλ‘ μ§μν©λλ€.
λ³΅ν© μλ£νμ μλ‘μ΄ ν λΉ.
var jeremy = await context.Authors.SingleAsync(author => author.Name.StartsWith("Jeremy"));
jeremy.Contact = new() { Address = new("2 Riverside", "Trimbridge", "TB1 5ZS", "UK"), Phone = "01632 88346" };
await context.SaveChangesAsync();
λ³΅ν© μλ£νμ μμ±λ§ ν λΉ.
var brice = await context.Authors.SingleAsync(author => author.Name.StartsWith("Brice"));
brice.Contact.Address = new("4 Riverside", "Trimbridge", "TB1 5ZS", "UK");
await context.SaveChangesAsync();
Array μΈλ±μ±
EF 8 μμλ Json Column μΌλ‘ μ§μ λ λ³΅ν© κ°μ²΄μ μ§ν© μμ±μ λν΄ Array Indexing μ΄ κ°λ₯ν΄μ‘μ΅λλ€.
νν°λ§
var cutoff = DateOnly.FromDateTime(DateTime.UtcNow - TimeSpan.FromDays(365));
var updatedPosts = await context.Posts
.Where(
p => p.Metadata!.Updates[0].UpdatedOn < cutoff
&& p.Metadata!.Updates[1].UpdatedOn < cutoff)
.ToListAsync();
ν¬μ¬
var postsAndRecentUpdates = await context.Posts
.Where(p => p.Metadata!.Updates[0].UpdatedOn != null
&& p.Metadata!.Updates[1].UpdatedOn != null)
.Select(p => new
{
p.Title,
LatestUpdate = p.Metadata!.Updates[0].UpdatedOn,
SecondLatestUpdate = p.Metadata.Updates[1].UpdatedOn
})
.ToListAsync();
μΊμ±λ μ§ν©μ ν΅ν νν°λ§
var searchTerms = new[] { "Search #2", "Search #3", "Search #5", "Search #8", "Search #13", "Search #21", "Search #34" };
var postsWithSearchTerms = await context.Posts
.Where(post => post.Metadata!.TopSearches.Any(s => searchTerms.Contains(s.Term)))
.ToListAsync();
λ³΅ν© μλ£ν(Complex Type)
EF 8μ λμ λ μ΄ κΈ°λ₯μ λ§ κ·Έλλ‘ λ³΅ν© μλ£νμ λ€λ£¨λ κΈ°λ₯μ μ΄λ¦μ λλ€.
λ³΅ν© μλ£νμ κ΄ν μ§μμ EF 7μμλ Json Column μΌλ‘, EF 8 μμλ Complex Type(κ³Ό Json Column) μΌλ‘ μ§μλλ κ²μ΄μ£ .
μ§μ λ°©λ²
Attribute
ComplexTypeAttribute λ₯Ό λͺ¨λΈ ν΄λμ€μ λΆμ¬ν©λλ€.
[ComplexType]
public class Address
{
public required string Line1 { get; set; }
public string? Line2 { get; set; }
public required string City { get; set; }
public required string Country { get; set; }
public required string PostCode { get; set; }
}
Fluent Api
EntityEntry.ComplexProperty() λ©μλλ₯Ό νΈμΆν©λλ€.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Customer>()
.ComplexProperty(e => e.Address);
modelBuilder.Entity<Order>(b =>
{
b.ComplexProperty(e => e.BillingAddress);
b.ComplexProperty(e => e.ShippingAddress);
});
}
μ΄λ κ² μ§μ λλ©΄, μ΄ λͺ¨λΈ ν΄λμ€μ μμ±λ€μ μμ κ°μ²΄μ νλλ‘ λ§΅νλ©λλ€.
INSERT INTO [Customers] ([Name], [Address_City], [Address_Country], [Address_Line1], [Address_Line2], [Address_PostCode])
OUTPUT INSERTED.[Id]
VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
μ΄ κΈ°λ₯μ,
-
Owner ν μ΄λΈμ νλλ‘ κ°μ μ€μ λλ€λ μ μμ Json Column κ³Ό λ€λ¦ λλ€.
λ°μ΄ν° λ² μ΄μ€μ Json νμ± κΈ°λ₯μ νΈμΆνμ§ μκΈ° λλ¬Έμ λ ν¨μ¨μ μ΄λΌκ³ ν μ μμ΅λλ€. -
μΈμ€ν΄μ€κ° νΉμ Ownerμ μνλ κ²μ΄ κ°μ λμ§ μλλ€λ μ μμ Owned μ λ€λ¦ λλ€.
νΉν, λ λ²μ§Έ νΉμ§μ Complex Typeμ μ‘΄μ¬μ μ΄μ λΌκ³ ν μ μμ΅λλ€.
Owned Type κ³Ό μ°¨μ΄μ
Owned Type μ Onwer μ PKλ₯Ό FK λ‘ λ³΄μ ν Entity μ΄κ³ , μ΄λ₯Ό λ°κΏ μ μμ΅λλ€.
μ΄λ¬ν νΉμ§μ μλμ κ°λ¨ν μ½λμ λ¬Έμ λ₯Ό μΌμΌν΅λλ€.
customer.Orders.Add(
new Order {
Contents = "Tesco Tasty Treats",
BillingAddress = customer.Address,
ShippingAddress = customer.Address,
});
await context.SaveChangesAsync();
warn: 8/20/2023 12:48:01.678 CoreEventId.DuplicateDependentEntityTypeInstanceWarning[10001] (Microsoft.EntityFrameworkCore.Update)
The same entity is being tracked as different entity types βOrder.BillingAddress#Addressβ and βCustomer.Address#Addressβ with defining navigations. If a property value changes, it will result in two store changes, which might not be the desired outcome.
// β¦
fail: 8/20/2023 12:48:01.709 CoreEventId.SaveChangesFailed[10000] (Microsoft.EntityFrameworkCore.Update)
An exception occurred in the database while saving changes for context type βNewInEfCore8.ComplexTypesSample+CustomerContextβ.
System.InvalidOperationException: Cannot save instance of βOrder.ShippingAddress#Addressβ because it is an owned entity without any reference to its owner. Owned entities can only be saved as part of an aggregate also including the owner entity.
// β¦
Customer.Address λ Customer μ μν κ°μ²΄μΈλ°, μ΄λ₯Ό Orger.Address μ ν λΉνλ κ²μ (μ½λλ‘λ λ¬Έμ κ° μ ν μμ§λ§) μμ κ΄κ³κ° νμ΄μ§λ€λ μ μ λ§νκ³ μμ΅λλ€.
Complex Type μ μ νν μ΄ λ¬Έμ λ₯Ό ν΄κ²°ν©λλ€.
λ¨μν Customerμ νλ κ°λ€μ΄, Order μ νλκ°μΌλ‘ 볡μ¬λλ κ²μ
λλ€.
λΆλ³μ±
κ·Έλ¬λ, μ£Όμν΄μΌ ν μ μ΄ μμ΅λλ€.
Complex Type μΈ κ²½μ°, μλμ μ½λλ₯Ό μ€ννλ©΄,
customer.Orders.Add(
new Order {
Contents = "Tesco Tasty Treats",
BillingAddress = customer.Address,
ShippingAddress = customer.Address,
});
customer.Address.Line1 = "Peacock Lodge";
await context.SaveChangesAsync();
Order.BillingAddress, Order.ShippingAddress, Customer.Addresss μ Line1 μμ±μ΄ μ λΆ βPeacock Lodgeβ λ‘ λ³κ²½λ©λλ€.
μ΄λ Address κ° μ°Έμ‘°ν κ°μ²΄μ΄κΈ° λλ¬Έμ λΉμ°ν κ²°κ³Όμ λλ€.
λ§μ½, μ΄ κ²μ΄ μνλ κ²°κ³ΌλΌλ©΄, Address λ Complext TypeμΌλ‘ μ μΈλλ©΄ μλκ³ , λ³λμ Entityλ‘ μ μΈλμ΄μΌ ν©λλ€.
λ§μ½, μνμ§ μλ κ²μ΄λΌλ©΄, Complex Type μ λΆλ³ νμ μΌλ‘ μ μΈνλ κ²μ΄ μ’μ΅λλ€.
- record class
- readonly record struct
μν νΈλνΉ
Complex Type μ EFμ μν΄ κ°μ΄ μΆμ λ©λλ€.
var billingAddress = context.Entry(order)
.ComplexProperty(e => e.BillingAddress)
.CurrentValue;
var postCode = context.Entry(order)
.ComplexProperty(e => e.BillingAddress)
.Property(e => e.PostCode)
.CurrentValue;
μ°Έμ‘°