|
|
Entity Framework 2012/05/08 17:10
PACKT에서 Entity Framework 4.1 에 관한 책을 내놓았다... 
본래 Cookbook 스타일은 크게 선호하는 편은 아니지만, 워낙에 EF 관련 서적들이 별루 없는 상황이라 (더구나 Code First가 중심인 책은 정말 찾기 어렵다) 반가운 마음이 앞선다. E-Book으로 어여 후다닥 읽어보고 후기 올려야겠군...
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/131
Entity Framework 2012/04/09 08:00
지난 글 Repository? DAO?에서 이미 얘기한대로 MyStory에서는 별도의 Data Access Layer (Jake님과 이일민님의 용어 지적에 다시한번 감솨~)를 두지 않을 계획이다.
이유는 이전 글에서도 밝힌대로 여러가지가 있지만, 하나 더 꼽으라면 데이터에 접근하는 코드나 쿼리의 재사용성이 과연 얼마나 필요한가? 란 질문에 자신있게 대답할 수 없기 때문이다. 지극히 개인적인 경험에서 우러나온 결론이지만, 과거 프로젝트에서 쿼리를 그대로 재사용하는 경우는 그렇게 많지 않았다.
설령 재사용이 필요한 모듈이 많다고 치더라도 그런 몇몇 모듈들 때문에 아키텍쳐 전체에 걸쳐 떡하니 레이어를 하나 더 추가하고 싶지는 않다.
즉, 재사용성을 줄이는 한이 있더라도 레이어가 하나 더 추가함으로써 발생되는 복잡성과 여러가지 부수적인 과제들을 떠안기 싫다는 결론에 도달했다.
그리고 또 하나, 부가적인 레이어를 추가하지 않더라도 쿼리나 공통 로직을 재사용할 수 있는 방법은 부지기수다.
재사용해야할 모듈은 그 형태와 종류가 매우 다양하리라고 예상된다. 예를 들면, 복잡한 쿼리 결과를 리턴하는 모듈일 수도 있고, 매우 단순한 필터만 필요한 쿼리일 수도 있으며, 또 커맨드 형태의 입력/수정용 명령일 수도 있을 것이다. 여기선 그런 방법중에서도 복잡한 쿼리 결과를 리턴해야하는 로직의 재사용성을 높일 수 있는 방법중에 하나인 Query Object 방식을 소개한다. 아래는 MyStory에서 사용하고 있는 PostQuery 클래스인데 Post를 가져와야할때 페이징 처리나 Filter 조건을 유연하게 처리할 수 있도록 하기 위해서 Filter 조건들을 Property로 가지고 있고 GetQuery 메소드를 통해 IQueryable<T> 형식을 리턴한다.
public
class
PostQuery : IQuery<Post>
{
public
int? CurrentPageNumber { get; set; }
public
int? PostsPerPage { get; set; }
public
string Tag { get; set; }
public
IQueryable<Post> GetQuery(MyStoryContext dbContext)
{
var query = dbContext.Posts.OrderByDescending(p => p.DateCreated).AsQueryable();
if (!string.IsNullOrWhiteSpace(Tag))
{
query = query.Where(p => p.Tags.Any(t => t.TagText == Tag));
}
if (CurrentPageNumber != null && PostsPerPage != null) {
query = query.Skip((CurrentPageNumber.Value - 1) * PostsPerPage.Value).Take(PostsPerPage.Value);
}
return query;
}
}
public
interface
IQuery<T> where T : class
{
IQueryable<T> GetQuery(MyStoryContext context);
}
그리고 아래는 HomeController와 TagController에서 각각 검색 조건을 달리하여 PostQuery 클래스를 사용하고 있는 모습이다.
HomeController.cs var posts = new
PostQuery()
{
CurrentPageNumber=page,
PostsPerPage=perPage
}
.GetQuery(dbContext)
.ToList();
TagController.cs var posts = new
PostQuery()
{
CurrentPageNumber = page,
PostsPerPage = perPage,
Tag=tag
}
.GetQuery(dbContext)
.ToList();
마틴 파울러가 말하는 Query Object 패턴의 LINQ 버전쯤 될라나.. 머 암튼 복잡한 조건식이 사용되면서 재사용해야 하는 조회용 모듈에는 꽤 적합해보인다.
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/129
Entity Framework 2012/04/03 13:41
아래와 같은 두개의 엔티티가 있다.
물론 MyStory에서 사용되고 있는 엔티티들이다.
public
class
Post
{
public Post()
{
this.Tags = new
List<Tag>();
}
public
int Id { get; set; }
public
string Title { get; set; }
public
string Content { get; set; }
public
DateTime DateCreated { get; set; }
public
DateTime DateModified { get; set; }
public
virtual
ICollection<Tag> Tags { get; set; }
}
public
class
Tag
{
public Tag()
{
this.Posts = new
List<Post>();
}
public
int Id { get; set; }
public
string TagText { get; set; }
public
virtual
ICollection<Post> Posts { get; set; }
}
그리고 Post와 Tag는 Many-to-Many 관계여서 아래와 같이 FluentAPI를 이용하여 맵핑하였다.
public
class
PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
// Table
this.ToTable("Posts");
// Properties
this.Property(p => p.Title)
.IsRequired()
.HasMaxLength(125);
this.Property(p => p.Content)
.IsRequired();
this.Property(p => p.DateCreated)
.IsRequired();
// Relationships
this.HasMany(p => p.Tags)
.WithMany(t => t.Posts)
.Map
(
m =>
{
m.MapLeftKey("PostId");
m.MapRightKey("TagId");
m.ToTable("TagPost");
}
); }
}
자, 이제 Tag 정보를 가져와야하는 쿼리를 구현해야 하는데, 정렬 기준은 Tag가 Post에 사용된 횟수를 기준으로 가져오는 것이다.
SQL을 사용하면 대략 아래와 같은 쿼리가 필요하지 싶다.
select a.tagid, a.cnt, b.tagtext
from (
select tagid,
count(*)
as cnt
from tagpost
group
by tagid ) a inner
join tags b on a.tagid = b.id
order
by cnt desc
뭐 별로 복잡하고 긴 SQL도 아니지만, 아래의 LINQ 쿼리와 비교해 보면,,,,
var tags = dbContext.Tags.OrderByDescending(t => t.Posts.Count).ToList();
음… LINQ가 SQL 쿼리보다 몇줄 덜 들고 하는 머 그런 얘기를 하려는건 아니다.
내가 얘기하고 싶은건, 개발자의 입장에서 보았을때 "어떤 방식이 훨씬 명료하며, 어떤 방식이 개발자의 의도를 더욱 뚜렷이 드러내고 있는가?" 이다.
뭐 이마저도 好不好가 있겠지만,,,
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/128
Nuget 2012/03/19 17:36
MyStory는 의존라이브러리들의 설치를 위해서 Nuget을 사용하는데, 머 너무나 편리한 방법이지만 의존 패키지들이 쌓일수록 packages 폴더의 사이즈가 계속해서 늘어나는 문제가 있다. MyStory만 하더라도 어느 순간 이미 40MB가 훌쩍 넘어 있었다. 이럴땐 자바의 Maven식 관리 방법이 살짝 부러워진다. 여기서 Maven 스탈이라 함은 내 로컬 PC의 패키지 저장소가 중앙 집중화(단 한군데 폴더로 셋팅) 되어 있는걸 말한다.
Nuget의 패키지 저장소는 각 솔루션 폴더의 바로 하위에 있기 때문에 소스 관리툴을 사용 하다보면 저 무거운 packages 폴더가 소스 관리 서버로 오르락 내리락 하는 부하(?)가 걸릴 수 있는데, 이럴땐 뭐,, GitHub를 사용한다면 .gitignore 파일을 통해서 packages 폴더를 소스 관리 서버에 안올리면 그만이지만,
* MyStory에서 사용하는 .gitignore 파일 설정 내용
TestResults
[Dd]ebug/
[Rr]elease/
[Tt]est*
[Oo]bj
[Bb]in
*.user
*.suo
*.sln.cache
Output/
*.user
/.gitversion.tmp
/.gitversion.short.tm
Thumbs.db
*.[Cc]ache
*.bak
*.ncb
*.log
*.DS_Store
[Tt]humbs.db
_ReSharper.*
*.resharper
Ankh.NoLoad
packages
소스를 내려받은후 nuget.exe 툴을 통해서 또 reinstall 해줘야하는 번거로움도 있고해서,,
* packages.config의 내용을 기반으로 패키지 설치
nuget install packages.config
방법을 찾아보았더니, 역시나.... 이미 있었군. ㅡ,.ㅡ
http://docs.nuget.org/docs/workflows/using-nuget-without-committing-packages
솔루션탐색기에서 솔루션 클릭하고 "Enable Nuget Package Restore" 누르면 끝... 넘 초간단이어서 허무하군...
클릭하고나면, .netget 폴더가 생기고 그 하위에 NuGet.targets라는 파일이 생기는데 이 파일을 통해 packages 폴더 위치 변경이 가능하고
(걘적으로 Maven 식의 관리 방식은 싫어라해서 그냥 packages 폴더는 솔루션 폴더 하위에 그대로 두었다.), VS에서 빌드를 할때마다, packages.config를 찾아서 없는 패키지들을 알아서 척척 다운받는단다..
NuGet Rocks !!!
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/126
분류없음 2012/03/08 17:39
기존에 회사에서 주로 작업하던 프로젝트는 구조가 천편일률 이었다.
맨 아래 계층엔 DB가 있고 그 위에는 DB를 액세스하는 Repository 레이어나 DAO 레이어가 있고 여기서는 주로 Hibernate와 같은 ORM을 사용해서 Persistence 작업을 한다 그리고 DAO/Repository 레이어 위에는 Service 레이어가 있고 여기서 주로 트랜잭션이 관리되고 대부분의 비즈니스 로직도 이곳에서 실행된다 그리고 최종적으로 Service 레이어에서 UI 레이어로의 데이터 반환은 DTO나 ViewModel을 사용한다.
뭐 너무나 틀에 박혀 있던 Layered Architecture라 모든 프로젝은 저렇게 진행되었다. 그 프로젝이 간단하건 복잡하건 어떤 성질의 어플이건 묻지도 따지지도 않고…
그런데 요즘 자꾸 딴 생각이 든다… 아니 이미 들어버린지 오래다.
그 딴 생각은,,,
- 왜 DAO나 Repository 레이어가 꼭 필요할까??
- 왜 굳이 DTO나 ViewModel을 써야할까? 그냥 도메인 모델을 리턴하면 안되??
첫번째에 대해선 이미 확고해진거 같다. 물론 내 나름의 결론은 되도록 쓰지말자!! 이다.
GoodBye DAO라는 자극적인 문구를 걸면서까지 Spring ROO에서 지향하고자하는 심플한 아키텍쳐나 Repository 얘기만 나오면 거품을 무는 Ayende의 썰들을 굳이 들이대지 않더라도 그동안 DAO나 Repository를 사용하면서 그 혜택들을 얼마나 누려왔나를 생각해보면 얼른 답이 나온다. 나에게 저 레이어들의 존재가 주는 가장 큰 장점은 Unit Test시에 Mock을 만들기 편하다는 것과 언제든지 Persistence 툴을 교체할 수 있다는 보험과 같은 믿음 두 가지였다. 하지만 솔직히 말하건데 아직까지 나는 어플리케이션 개발 이후에 Persistence 툴을 바꿔 본 적이 단 한번도 없었다. 그리고 Unite Test, 여기에 대해선 좀 논란이 있을 수도 있는데 적어도 나에겐 그리고 내가 개발했던 프로젝트들(UI를 통해 데이터를 입력하고 보여주는 일반적인 엔터프라이즈 어플들이 대부분이었다)에서는 Service 레이어나 DAO, Repository 레이어들을 Unite Test 하는 것이 얼마나 효과적이었나? 얼마나 어플리케이션의 버그를 줄이는데 이바지 했나?에 대해서 많은 의구심이 든다. 차라리, Mock을 사용하지 않고 실구현체를 통해 Controller나 Service에서 DB까지 테스트하는 Functional Test나 Integration Test가 훨씬더 효율적이었다는 생각이 든다. 게다가 DAO나 Repository를 통한 과도한 추상화로 인해 EF나 NHibernate에서 제공하는 화려한 신공들이 빛을 보지못하는 상황까지 이르게 된다.
그래~서 DAO out!! Repository out!!! 에 도달하게 된다.
그리고 두번째 딴생각에 대한 내 결론은 경우에 따라서 도메인 모델을 그대로 리턴하자!!! 이다.
DTO나 ViewModel이 없으면?? 당장 떠오르는 것은 아마도 View와 Server 측의 커플링 문제, Lazy Loading 예외 문제, N+1로 인한 퍼포먼스 하락과 같은 것들일 것이다. 물론 120% 이해간다. 하지만, 그렇지 않은 성질의 어플들도 있지 않을까? 예를 들어 UI가 통계성 화면이 많지 않은 웹어플, UI 측에서 데이터의 가공을 쉽게 지원해주는 WPF와 같은 UI 프레임웍을 사용하는 어플, Disconnected 환경이 아닌 어플 등에선 굳이 뷰레이어를 위한 전용 모델이 필요할까하는 생각이 든다. 그리고 N+1의 문제는 EF의 경우엔 Include(), (N)Hibernate의 경우엔 FetchMany()를 사용하여 Eager Loading 기법을 사용하면 쉽게 해결될 수 있는 문제들이다. 그럼에도 불구하고 DTO나 ViewModel의 사용은 여전히 장점이 많다. 따라서 "프로젝트의 성질에 따라서 결정하자"와 같은 박쥐같은 결론에 도달했다. ㅋㅋㅋ
아무튼, 앞으로 이런 아이디어들을 포함한 몇가지 아키텍쳐와 관련된 오래되고 관행적인 기법들에 대해서 현재 짬짬이 플젝으로 진행하고 있는 오픈 소스 플젝을 통해 실험하고 테스트해볼 생각이다.
이 플젝을 잠시 소개하자면, 제목은 MyStory
머 그냥 아주 심플한 블로그 만드는 어플인데, 이걸로 뭐 대단한 블로그 엔진을 만들어 보겠다는 그런 거창한 생각은 아니고, 단지 관심가는 이런 저런 기술들을 테스트해보기 위한 목적이 크다.
구조도 대단히 단순하고 주로 사용되는 기술은 ASP.NET MVC와 Entity Framework Code First 그리고 UI를 만들기 위한 Bootstrap from Twitter, jQuery 정도이다.
이넘도 어디까지나 오픈 소스 플젝이라 당근 누구나 참여 가능하니 언제든 포크질 하시라~ 특히 심심풀이 코딩꺼리가 필요하거나 아키텍쳐에 대한 골머리를 앓고 싶으신분 대환영~~
Trackback 1
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/122
ASP.NET MVC 2012/03/02 12:01
까먹지말자 !!!
Razor 로 View 페이지를 만들 경우, 페이지에서 필요한 네임스페이스를 추가하는 방법은 두가지
1. @using 키워드를 사용해서 View 페이지에서 직접 네임스페이스를 추가하거나,,
2. 모든 웹페이지에서 공통으로 사용하기 위해서는 기존과 마찬가지로 web.config의 namespaces 섹션에 추가하면되는데, 이 때 주의할 것은 루트 폴더에 있는 Web.Config가 아니고 Views 폴더에 있는 Web.config에 추가해야 한다는것. (생각외로 기존 aspx 뷰를 쓸때와 다른게 많쿤...)
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/121
Entity Framework 2012/03/02 09:58
Let's assume that we have 3 tables as follow :
Users and Bookings tables have FK relationship via UserId which is NOT NULL, meanwhile there is BookingUsers table for many-to-many relationship between Users and Bookings table, it is so simple and straightforward.
Ok, now let's add cascade option on Bookings.UserId, BookingUsers.UserId and BookingUsers.BookingId columns. Once you try to add third cascade option on any column you will face following error messages.
'Bookings' table saved successfully
'BookingUsers' table
- Unable to create relationship 'FK_BookingUsers_Booking'.
Introducing FOREIGN KEY constraint 'FK_BookingUsers_Booking' on table 'BookingUsers' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors
This is called multiple cascade paths issue.
I don't know if it is prevented in other databases such as Oracle and MySQL, but SQL server does not allow this.
It seems reasonable, however, from Entity Framework's point of view, it is somewhat strange, let's see how I tried to design entity model in ORM level.
public class Booking
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
public ICollection<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public ICollection<Booking> Bookings { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasMany(u => u.Bookings)
.WithMany(b => b.Users)
.Map(m =>
{
m.ToTable("BookingUsers");
m.MapLeftKey("UserId");
m.MapRightKey("BookingId");
});
}
It seems ok firstly, however, it will throw SqlException that address multiple cascade paths issue as mentioned above
In order to solve this issue we have two options.
First option, just set Booking.UserId property as nullable as "public int? UserId {get; set; }"
Second option, set Booking.UserId property as not nullable, instead turn off cascade delete using FluentAPI as follow :
modelBuilder.Entity<Booking>()
.HasRequired(b => b.User)
.WithMany()
.HasForeignKey(b => b.UserId)
.WillCascadeOnDelete(false);
Hope this helps !!
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/109
Entity Framework 2012/02/22 17:41
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(1) - 테이블 및 컬럼 이름의 명시적 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(2) - 데이터 길이 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(3) - Computed & Identity 컬럼
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(4) - Value Object
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(5) - 동시성 관리
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(6) - Relationship
Relationship
앞서 잠시 설명했듯이 모든 엔티티는 키가 되는 프로퍼티를 가지고 있어야 한다. 물론 이 키 프로퍼티는 데이터베이스 테이블에서 프라이머리키로 맵핑 되며, 이 때 EF 4.1에서 제공하는 기본 컨벤션과 위배되는 이름의 프로퍼티를 사용하기 위해서는 지난 6월호에서 설명했듯이 Key 어트리뷰트를, 또 외래키를 위해서는ForeignKey 어트리뷰트를 사용한다.
그런데 만일 어떤 엔티티가 특정 엔티티에 대해서 여러 개의 레퍼런스를 가지고 싶을 경우는 어떻게 해야 할까? 예를 들어, 하나의 Employee가 Team에 대하여 FirstTeam, SecondTeam과 같이 다수의 레퍼런스를 가지고자 할 때가 그런 경우이다. 마찬가지로 Employee가 자기 자신에 대해 레퍼런스를 가지고자 할 때도 마찬가지의 경우이다. 이럴 때 사용할 수 있는 방법이 InverseProperty 어트리뷰트이다. 사용 방법은 <리스트 16>의 예제 코드와 같다.
public class Employee
{
[Key]
public int EmployeeKey { get; set; }
public string Name { get; set; }
[ForeignKey("FirstTeam")]
public int FirstTeamKey { get; set; }
[InverseProperty("FirstEmployees")]
public Team FirstTeam { get; set; }
[ForeignKey("SecondTeam")]
public int SecondTeamKey { get; set; }
[InverseProperty("SecondEmployees")]
public Team SecondTeam { get; set; }
}
public class Team
{
[Key]
public int TeamKey { get; set; }
public string TeamName { get; set; }
public virtual ICollection<Employee> FirstEmployees { get; set; }
public virtual ICollection<Employee> SecondEmployees { get; set; }
}
<리스트 16> InverseProperty 어트리뷰트를 사용하여 다중 레퍼런스에 대한 맵핑을 설정하고 있다. 그러나 런타임 에러가 발생할 것이다.
참고로 InverseProperty는 양쪽 Entity 중 한군데에만 설정해도 되나 모델간의 관계를 좀 더 명확히 하기 위해서 양쪽 모두에 선언해도 좋다. 그런데, 예상과는 달리 실제로 <리스트 16>의 코드를 실행시키면 예외가 발생하게 된다. 이유는 위의 설정에 의해서 생성되는 FirstTeamKey와 SecondTeamKey 외래키는 Not Null로 테이블에 생성되게 되는데 지금의 상황은 Employees 테이블 내의 두 컬럼이 동일한 Teams 테이블의 데이터를 참조하고 있으면서, 동시에 EF 4.1은 Casecade Delete 옵션을 디폴트로 On 시켜버리므로 서로간의 관계가 상충되는 상황이 되기 때문이다. 이 때는 <리스트 17>처럼 Fluent API를 사용해서CasecadeOnDelete 옵션을 꺼버리거나 두 외래키를 Nullable로 설정하는 방법이 있다.
modelBuilder.Entity<Employee>()
.HasRequired(e => e.FirstTeam)
.WithMany(t => t.FirstEmployees)
.HasForeignKey(e => e.FirstTeamKey)
.WillCascadeOnDelete(false);
<리스트 17> 현재는 오직 Fluent API를 사용해야만 CasecadeOnDelete 옵션 설정이 가능하다.
이번 글에서는 Data Annotations API와 Fluent API의 주요 기능들을 살펴 보았다. 그러나, 실제 모델간의 관계는 이보다 훨씬 복잡하고 다양하다. 지면상 좀 더 다양한 관계를 다루어 보진 못했지만, 필자의 블로그에서 계속해서 이에 대한 논의를 이어가도록 하겠다. 다음 시간에는 EF 4.1에서 변화된 주요 API를 계속 살펴 보도록 하겠다.
p.s 본 기사의 저작권은 필자(권효중)만이 아닌 마이크로소프트웨어에게도 있습니다. 아쉽지만 저작권자의 허락없이 글 내용을 그대로 복사하는 것은 자제해주시길 부탁드립니다.
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/120
Entity Framework 2012/02/22 17:41
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(1) - 테이블 및 컬럼 이름의 명시적 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(2) - 데이터 길이 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(3) - Computed & Identity 컬럼
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(4) - Value Object
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(5) - 동시성 관리
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(6) - Relationship
동시성 관리
EF 4.1은 동시성 충돌 문제를 해결하기 위해 낙관적 동시성 모델을 지원하며 두 가지 어트리뷰트를 제공하고 있는데 Timestamp와 ConcurrencyCheck 어트리뷰트가 그것이다. 우선 낙관적 동시성 모델의 개념을 짧게 설명하자면, 데이터베이스의 어떠한 값에 대하여 동시에 서로 다른 사용자가 수정하고자 할 때 이 행위를 그냥 허용해주고 다른 방법으로 해결하게끔 하는 방법이다. 이와 반대로, 비관적 동시성 모델은 어떤 사용자가 값을 수정하고 있으면 이 값에 대해 잠금을 걸어서 다른 사용자는 수정할 수 없도록 하는 것이다.
<리스트 11>은 Name 프로퍼티에 ConcurrencyCheck 어트리뷰트를 선언하여 동시성 관리에 참여하도록 하고 있다.
public class Employee
{
public int Id { get; set; }
[ConcurrencyCheck]
public string Name { get; set; }
}
<리스트 11> ConcurrencyCheck 어트리뷰트를 사용한 낙관적 동시성 관리
ConcurrencyCheck 어트리뷰트는 한 클래스 안에 여러 프로퍼티에 대해서 선언할 수 있다. 이렇게 동시성 관리에 참여하게 되는 프로퍼티들은 추후에 update 쿼리가 데이터베이스로 실행될 때 Where 절에 참여하게 된다. 예를 들면, <리스트 12>에서 두 번째 context.SaveChanges()가 실행될 때 실제로 수행될update 쿼리문에는 update employees set name = ‘이청용’ where id=1 and name=’박지성’ 과 같이name 컬럼이 where 절에 참여하게 되는 것이다. 이 때 name의 값은 ‘이청용’으로 변경되기 전 원래의 값인 ‘박지성’이 포함된다.
try
{
Company context = new Company();
var newEmp = new Employee { Name = "박지성" };
context.Employees.Add(newEmp);
context.SaveChanges();
newEmp.Name = "이청용";
// 아래 라인에 브레이크를 걸고 데이터베이스에서 Name 값을 변경한 후 다시 디버깅을 재개하면 DbUpdateConcurrencyException 예외가 발생한다.
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine(ex.ToString());
}
<리스트 12>
EF 4.1에서는 최초 쿼리에서 가져온 값을 original value, 현재 컨텍스트에 로드 되어 있는 값을 current value라고 하는데 ConcurrencyCheck 어트리뷰트는 동시성 관리를 위해서 original value를 비교한다. 따라서, original value를 유지할 수 없는 비연결 기반의 어플리케이션(예를 들면, 웹 어플리케이션)에서는<리스트 13>처럼 업데이트 시점에 컨텍스트에게 original value를 알려주어야만 동시성 관리가 가능하다.
[HttpPost]
public ActionResult Edit(int id, string originalName, Employee employee)
{
if (ModelState.IsValid)
{
db.Entry(employee).State = EntityState.Modified;
db.Entry(employee).Property(e => e.Name).OriginalValue = originalName;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(employee);
}
<리스트 13> ASP.NET MVC 어플리케이션 컨트롤러의 Action 메소드에 original value를 명시적으로 알려주고 있다.
앞서 언급한 것처럼 ConcurrencyCheck 어트리뷰트는 클래스 내에서 동시성 관리의 필요성이 높은 프로퍼티들에 모두 선언해 줄 수 있다. 대신에 <리스트 13>처럼 비연결 기반의 어플리케이션에서는 original value에 대해 개발자가 직접 관리해 주어야 하는 부담이 있을 수 있다.
이에 반해 Timestamp 어트리뷰트는 동시성 관리의 목적으로 아예 별도의 컬럼을 제공하는 방법이다.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] Version { get; set; }
}
<리스트 14> Timestamp 어트리뷰트를 사용한 동시성 관리
Timestamp 어트리뷰트는 클래스 내에서 하나의 프로퍼티에만 사용이 가능하다. Timestamp 어트리뷰트도 마찬가지로 update 쿼리가 수행될 때 where 절에 해당 컬럼이 참여하여 값을 비교하는 원리는 동일하다. ConcurrencyCheck 어트리뷰트와 비교했을 때, 비연결 기반의 어플리케이션에서 original value 값을 일일이 컨텍스트에 제공하지 않아도 된다는 편리함이 있다.
Fluent API로 설정하는 방법은 <리스트 15>와 같다.
// ConcurrencyCheck 어트리뷰트와 동일
modelBuilder.Entity<Employee>()
.Property(e => e.Timestamp)
.IsConcurrencyToken();
// Timestamp 어트리뷰트와 동일
modelBuilder.Entity<Employee>()
.Property(e => e.Timestamp)
.IsRowVersion();
<리스트 15> Fluent API를 사용한 동시성 관리
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/119
Entity Framework 2012/02/22 17:41
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(1) - 테이블 및 컬럼 이름의 명시적 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(2) - 데이터 길이 지정
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(3) - Computed & Identity 컬럼
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(4) - Value Object
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(5) - 동시성 관리
Entity Framework 4.1 - DataAnnotation과 Fluent API를 활용한 모델 설정(6) - Relationship
Value Object
Domain Driven Design의 관점에서 각각의 엔티티 모델들은 그들을 구분 지을 수 있는 키 프로퍼티를 가지고 있어야 한다. 따라서 Entity Framework도 역시 앞서 우리가 만든 Employee 클래스를 하나의 엔티티로 인식하기 위하여서는 반드시 키가 되는 프로퍼티를 요구한다. 그러나 Domain Driven Design에서 말하는Value Object에는 별도의 키가 존재하지 않는다. 이를 위해 EF 4.1은 ComplexType이라는 어트리뷰트를 제공하고 있다.
<리스트 8>을 보자. Employee 클래스는 단순 주소 값을 가지기 위해 Address 클래스 타입의HomeAddress라는 프로퍼티를 선언했다.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public Address HomeAddress { get; set; }
}
[ComplexType]
public class Address
{
public string City { get; set; }
public string Street { get; set; }
}
<리스트 8> Address 타입의 HomeAddress 프로퍼티를 선언
<그림 4>는 Employee 엔티티가 데이터베이스 테이블로 생성된 그림을 보여주고 있다. HomeAddress 프로퍼티가 각각 두 개의 컬럼으로 생성되었다.
<그림 4> Address 타입의 HomeAddress 프로퍼티가 생성되었다.
여기서 몇 가지 알아두어야 할 사항이 있다. EF 4.1은 ComplexType 어트리뷰트가 선언되어 있지 않더라도 위의 경우에 컨벤션을 통해서 Address 클래스를 ComplexType으로 설정한다는 것이다. 따라서 굳이Address 클래스에 ComplexType 어트리뷰트를 선언할 필요는 없다. 하지만, 만일 Address 클래스에 Id와 같은 프로퍼티를 필요로 할 때는 명시적으로 ComplexType 어트리뷰트를 선언해 주어야 한다. 그렇지 않으면 EF는 Address 클래스를 Value Object가 아닌 Entity로 취급할 것이다. 또 하나 알아두어야 할 것은<리스트 9>에서 보듯이 ComplexType 프로퍼티에 대해서는 지정할 값이 없더라도 언제나 인스턴스를 생성해 주어야 한다는 것이다.
Company context = new Company();
var newEmp = new Employee
{
Name = "김성기",
HomeAddress=new Address()
};
context.Employees.Add(newEmp);
context.SaveChanges();
<리스트 9> ComplexType의 프로퍼티엔 항상 인스턴스를 생성해 주어야 한다.
Fluent API로는 <리스트 10> 처럼 간단히 지정하면 ComplexType으로 설정된다.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Address>();
}
<리스트 10> Fluent API를 사용해서 ComplexType 지정
Trackback 0
:
Trackback Address :: http://funnygangstar.tistory.com/trackback/118
|