[EntityFramework Core] 관계 1 시도
47925 단어 csharpaspnetcoreentityframeworkcore
소개
이번에는 외래 키 설정, "포함"방법 등과 같은 관계에 대해 EF Core를 사용해 보겠습니다.
환경
DB 테이블
DB 마이그레이션
먼저 DB 마이그레이션 시 외래키 설정을 해보도록 하겠습니다.
속성만 포함(ID 없음)
Author.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BookStoreSample.Models
{
[Table("author")]
public record Author
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
// Many to 1
public List<Book> Books { get; init; } = new List<Book>();
}
}
Book.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BookStoreSample.Models
{
[Table("book")]
public record Book
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
// 1 to many
public Author? Author { get; init; }
// Many to 1
public List<BookPrice> BookPrices { get; init; } = new List<BookPrice>();
// Many to many
public List<BookStore> BookStores { get; init; } = new List<BookStore>();
}
}
BookStore.cs
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BookStoreSample.Models
{
[Table("book_store")]
public record BookStore
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
// Many to many
public List<Book> Books { get; init; } = new List<Book>();
}
}
BookStoreContext.cs
using Microsoft.EntityFrameworkCore;
namespace BookStoreSample.Models
{
public class BookStoreContext: DbContext
{
public BookStoreContext(DbContextOptions<BookStoreContext> options): base(options)
{
}
public DbSet<Author> Authors => Set<Author>();
public DbSet<BookStore> BookStores => Set<BookStore>();
public DbSet<Book> Books => Set<Book>();
public DbSet<BookPrice> BookPrices => Set<BookPrice>();
}
}
결과
마이그레이션을 통해 외래 키를 추가하려는 경우 아래와 같은 작업을 수행할 필요가 없습니다.
BookStoreContext.cs
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.HasOne(b => b.Author!)
.WithMany(a => a!.Books)
.HasForeignKey(b => b.AuthorId);
}
...
ID만 있는(속성 없음)
Author.cs
...
[Table("author")]
public record Author
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
}
...
Book.cs
...
[Table("book")]
public record Book
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
[Required]
[Column("author_id")]
[ForeignKey("author")]
// 1 to many
public int AuthorId { get; init; }
...
BookStore.cs
...
[Table("book_store")]
public record BookStore
{
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; init; }
[Required]
[Column("name")]
public string Name { get; init; } = "";
}
...
"book"테이블과 "book_store"테이블을 연결하는 데이터가 없기 때문에 "stored_book"테이블을 추가합니다.
StoredBook.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BookStoreSample.Models
{
[Table("stored_book")]
public record StoredBook
{
[Required]
[Column("bookstore_id")]
[ForeignKey("book_store")]
public int BookStoreId { get; init; }
[Required]
[Column("book_id")]
[ForeignKey("book")]
public int BookId { get; init; }
}
}
결과
"ForeignKey"속성으로 외래 키를 추가할 수 없습니다.
요약
만들다
물론 참조 테이블의 ID로 새 레코드를 삽입할 수 있습니다.
BookService.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BookStoreSample.Applications;
using BookStoreSample.Models;
using Microsoft.EntityFrameworkCore;
namespace BookStoreSample.Books
{
public class BookService: IBookService
{
private readonly BookStoreContext context;
public BookService(BookStoreContext context)
{
this.context = context;
}
public async Task<UploadResult> CreateBookAsync()
{
using var transaction = await context.Database.BeginTransactionAsync();
try
{
Author author = await context.Authors.FirstAsync(a => a.Id == 2);
Book newBook = new Book
{
Name = "SampleBook",
AuthorId = author.Id,
};
await context.Books.AddAsync(newBook);
await context.SaveChangesAsync();
var newPrice = new BookPrice
{
BookId = newBook.Id,
Price = 2000m,
StartDate = DateTime.Today,
};
await context.BookPrices.AddAsync(newPrice);
await context.SaveChangesAsync();
await transaction.CommitAsync();
return UploadResultFactory.GetSucceeded();
}
catch(Exception ex)
{
await transaction.RollbackAsync();
return UploadResultFactory.GetFailed(ex.Message);
}
}
}
}
하지만 테이블 인스턴스와 함께 삽입할 수도 있습니다.
BookService.cs
...
public async Task<UploadResult> CreateBookAsync()
{
using var transaction = await context.Database.BeginTransactionAsync();
try
{
Author author = await context.Authors.FirstAsync(a => a.Id == 2);
Book newBook = new Book
{
Name = "SampleBook",
Author = author,
BookPrices = new List<BookPrice>
{
new BookPrice
{
Price = 3000m,
StartDate = DateTime.Today
}
}
};
await context.Books.AddAsync(newBook);
await context.SaveChangesAsync();
await transaction.CommitAsync();
return UploadResultFactory.GetSucceeded();
}
catch(Exception ex)
{
await transaction.RollbackAsync();
logger.LogError(ex.Message);
return UploadResultFactory.GetFailed(ex.Message);
}
}
...
ID 변경
삽입하기 전에 일부 ID를 변경하면 어떻게 됩니까?
기본 키 변경
기본 키를 변경할 수 없습니다. 아니면 예외가 발생합니다.
BookService.cs
...
Author author = await context.Authors.FirstAsync(a => a.Id == 2);
author.Id = 30;
Book newBook = new Book
{
Name = "SampleBook",
AuthorId = author,
};
await context.Books.AddAsync(newBook);
await context.SaveChangesAsync();
...
결과
The property 'Author.Id' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key, first delete the dependent and invoke 'SaveChanges', and then associate the dependent with the new principal.
다른 레코드를 참조하는 기존 레코드 추가
BookService.cs
...
Author author = await context.Authors.FirstAsync(a => a.Id == 2);
BookPrice existedPrice = await context.BookPrices.FirstAsync(p => p.BookId == 2);
Book newBook = new Book
{
Name = "SampleBook",
Author = author,
BookPrices = new List<BookPrice>
{
existedPrice
}
};
await context.Books.AddAsync(newBook);
await context.SaveChangesAsync();
...
결과
새 "BookPrice"레코드가 삽입되지 않았습니다.
그러나 "existedPrice"의 "BookId"는 "newBook"의 ID로 변경되었습니다.
읽다
EF Core에서 기본적으로 자식 테이블은 부모 레코드를 자동으로 포함하지 않습니다.
BookService.cs
...
public async Task<List<BookStore>> GetBookStoresAsync()
{
return await context.BookStores.ToListAsync();
}
...
결과
[{
"id":1,
"name":"Store1",
"books":[]
},
{
"id":2,
"name":"Store2",
"books":[]
},
{
"id":3,
"name":"Store3","books":[]}]
"Include()"를 사용하여 하위 테이블을 포함할 수 있습니다.
BookService.cs
...
public async Task<List<BookStore>> GetBookStoresAsync()
{
return await context.BookStores.Include(s => s.Books)
.ToListAsync();
}
...
결과
[{
"id":1,
"name":"Store1",
"books":[{
"bookStoreId":1,
"bookId":2,
"book":null
}]
}]
손자 포함?
"ThenInclude()"를 사용하여 손자를 포함할 수 있습니다.
BookService.cs
...
public async Task<List<BookStore>> GetBookStoresAsync()
{
return await context.BookStores.Include(s => s.Books)
.ThenInclude(s => s.Book)
.ThenInclude(b => b!.Author)
.ToListAsync();
}
...
결과
[{
"id":1,
"name":"Store1",
"books":[{
"bookStoreId":1,
"bookId":2,
"book":{
"id":2,
"name":"SampleBook",
"authorId":2,
"author":{
"id":2,
"name":"SampleAuthor",
"books":[{
"id":3,
"name":"SampleBook",
"authorId":2,
"bookPrices":[],
"stores":[{
"bookStoreId":1,
"bookId":3
}]
}]
...
자원
Reference
이 문제에 관하여([EntityFramework Core] 관계 1 시도), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/masanori_msl/entityframework-core-try-relationship-1-2ppn텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)