EF3.5에서 “in” 쿼리문 표현을 위한 Contains 메서드 버그(?) 대응법
출처 : http://blogs.msdn.com/b/alexj/archive/2009/03/26/tip-8-writing-where-in-style-queries-using-linq-to-entities.aspx
LINQ로 “in” sql 구문을 사용하려면 Contains 확장 메서드를 사용하면 된다.
sql : select * from People where Firstname in {‘Alex’, ‘Colin’, ‘Danny’, ‘Diego’}
linq : var matches = from person in people
where names.Contains(person.Firstname)
select person;
그러나 Entity Framework 3.5에서 이 linq 구문은 Conains 메서드에서 다음과 같은 예외를 뱉어낸다.
LINQ to Entities에서 ‘Boolean Contains[Int32](System.Collections.Generic.IEnumerable`1[System.Int32], Int32)’ 메서드를 인식하지 않으므로 이 메서드는 저장소 식으로 변환될 수 없습니다.
이 문제는 EF4.0에서 발생하지 않는다. EF3.5에서도 실제 DB에 질의를 할 경우에 발생한다.
EF3.5에서 이 문제를 해결하기 위해서는 다음과 같은 표현식으로 작성하는 수 밖에 없다고 한다.
var matches = from person in people where
person.Firstname == “Alex” ||
person.Firstname == “Colin” ||
person.Firstname == “Danny” ||
person.Firstname == “Diego”
select person;
이 코드를 매번 작성하려면 어지간히 골치아픈일이 아닐 수 없다. 그래서 이러한 표현식을 만들어주는 메서드를 제시하고 있다.
public static Expression<Func<TElement, bool» BuildOrExpression<TElement, TValue>(
Expression<Func<TElement, TValue» valueSelector,
IEnumerable<TValue> values)
{
if (null == valueSelector)
throw new ArgumentNullException(“valueSelector”);
if (null == values)
throw new ArgumentNullException(“values”);
ParameterExpression p = valueSelector.Parameters.Single();
if (!values.Any())
return e => false;
var equals = values.Select(
value => (Expression)Expression.Equal(
valueSelector.Body,Expression.Constant(value, typeof(TValue))
)
);
var body = equals.Aggregate<Expression>(
(accumulate, equal) => Expression.Or(accumulate, equal)
);
return Expression.Lambda<Func<TElement, bool»(body, p);
}
[ASP.NET MVC] Areas 사용시 ActionLink 방법
Areas를 이용할 경우 ActionLink 사용시 다른 Areas로 이동하기 위한 별도의 매개변수가 보이지 않아 당황하게 된다. 이 경우 area 지정을 위한 별도의 매개변수가 존재하지 않지만, Route에 대한 이해만 가지고 있으면 해법이 보이게 된다. url 요청 정보는 RouteData에 캡슐화 되는데, 이 부분을 이용하면 간단하게 작성이 가능하다.
Page 속성인 RouteData에는 RequestContext의 RouteData를 가져온다. 여기에는 Controller와 Action 값이 등록되어 있다. Url을 생성할때도 이 RouteData를 토대로 작성되어 지는데, 파라메터가 추가된 경로를 생성하기 위해 종종 RouteValueDictionary(IDictionary를 구현하는…)를 설정해 주기도 한다.
<%:Html.ActionLink(“수정하기”, “Modify”, new { id = 1 }) %>
{controller}/{action}/{id} 형식과 같은 기본 Route가 설정되어 있을 경우, url은 다음과 같이 생성될것이다.
http://[domain]/Board/Modify/1
만약 new { id = 1 } 대신 new { articleId = 1} 을 사용할 경우 url은…
http://[domain]/Board/Modify?articleId=1
이와 같이 생성될것이다.
마찬가지로 new { area = “admin”} 을 설정해보면…
“admin” 이라는 이름을 가진 영역이 있는지를 찾게 된다. 예를 들어 관리자 페이지가 admin이라는 영역에 들어있을 경우, 관리자 페이지 접근을 위한 ActionLink는 다음과 같이 작성 가능하다.
<%:Html.ActionLink(“관리자 접속”, “Index”, “Home”, new { area = “admin” }, null) %>
이 경우 admin이라는 이름을 가진 영역의 Home 컨트롤러의 Index 액션메서드를 호출하게 된다.
MS SQL 쿼리 실행 이력 조회해보기…
LINQ 이녀석이 도대체 무슨 쿼리를 어떻게 생산해내고 있는지 까보고 싶어졌다. 아래 쿼리문은 실행된 SQL 쿼리문 이력을 조회해볼 수 있다. 이 데이터를 토대로 LINQ가 무슨 쿼리를 만들어 내고 있는지 확인 해볼 수 있겠다.
select
sqlText.text,
queryStats.last_execution_time,
queryStats.creation_time,
queryStats.execution_count,
queryStats.total_elapsed_time
from sys.dm_exec_query_stats as queryStats
cross apply sys.dm_exec_sql_text(queryStats.plan_handle) as sqlText
order by queryStats.last_execution_time desc;
sys.dm_exec_query_stats는 시스템 뷰로 다양한 쿼리 실행 결과정보가 포함되어 있다. 한번 확인해보면 좋겠다.
ASP.NET MVC Areas 이용시, 동일한 Controller 이름으로 인한 문제
가령 쇼핑몰에서 관리자용 서비스와 파트너사를 위한 별도의 Areas를 구성했다고 하자. 이 경우 사용자, 관리자 그리고 파트너 별로 별도의 Home Controller를 가진다고 하자. 물론 사용자의 경우 Home, 관리자의 경우 Admin 그리고 파트너의 경우 Partenr로 시작 컨트롤러를 라우트에 설정할 수는 있겠으나, 특정 엔터티와 관련되어 CRUD 작업을 하게되는 컨트롤러의 이름을 각각 지정하기란 그리 명료하지도 않고 오히려 혼란을 초래할 수 있다.
예를 들어 상품에 관한 엔터티의 경우 사용자는 조회작업을 하게될테고, 관리자의 경우 전체 상품에 관한 관리를 하게 된다. 그리고 파트너의 경우 자신이 직접 등록한 상품에 대해서 관리를 하게된다고 보자. 여기에 관련된 컨트롤러 이름을 ItemController라고 하면, 각 Areas별로 이러한 중복된 이름의 Controller가 존재하게 될것이다. 이러한 경우에도 각각 별도의 명명을 붙인다면, 그리고 이러한 경우의 컨트롤러가 많아진다면… 분명 혼란스러울테고, 명명법 측면에서도 그리 바람직하지 않으리라 본다.
시작 페이지의 경우 HomeController가 서비스 루트와 그리고 각각의 Areas 마다 존재하게 될것이다. 이 경우 아래와 같은 오류를 만나게 된다.
이름이 ‘Home’인 컨트롤러와 일치하는 여러 형식이 있습니다. 이 요청을 서비스하는 경로(‘{controller}/{action}/{id}’)에서 요청과 일치하는 컨트롤러를 검색하기 위해 네임스페이스를 지정하지 않은 경우 발생할 수 있습니다. 이 경우에는 ‘namespaces’ 매개 변수를 가지는 ‘MapRoute’ 메서드의 오버로드를 호출하여 이 경로를 등록합니다.’Home’에 대한 요청에서 다음과 같은 일치하는 컨트롤러를 찾았습니다.
Shop.Controllers.HomeController
Shop.Areas.Partner.Controllers.HomeController
Shop.Areas.Admin.Controllers.HomeController
즉, Home을 처리하는 컨트롤러가 3군데 있는데 모호하다 이거다. 이 문제를 해결하기 위해 메시지에 나온것 처럼 네임스페이스를 등록하여 전달할 컨트롤러를 제한할 수 있다. 우선 기존의 MapRoute 등록코드 부터 보면…
routes.MapRoute(
"Default", // 경로 이름
"{controller}/{action}/{id}", // 매개 변수가 있는 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional } // 매개 변수 기본값
);
그리고 MapRoute 오버로드된 인수를 살펴보다보면 다음과 같은 녀석이 있다.

마지막 파라메터를 보면 문자형 배열로 네임스페이스를 전달 할 수 있다는걸 알 수 있다. 전달해보자면…
routes.MapRoute(
"Default", // 경로 이름
"{controller}/{action}/{id}", // 매개 변수가 있는 URL
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // 매개 변수 기본값
new[] { "Shop.Controllers"}
);
이렇게 작성하고 빌드 후 실행시켜 보면… 정상적으로 렌더링된 첫 페이지를 만날 수 있다.
참조
관리자용 서비스를 위해 ASP.NET MVC에서 Areas(영역) 이용
ASP.NET MVC를 이용한 서비스 모델 개발시 관리자 영역을 구분할때 좀 애매모한 상황에서 고민하게 된다.
컨트롤러에서 각 액션메서드마다 필터로 접근 제한이 가능하기 때문에, 전통적인 방식과 같이 따로 Admin라는 폴더를 구성하고 그 안에 관리자용 서비스를 몰아둘 필요가 없어졌다. 혹자는 심지어 이제 그러한 관리자 페이지에 대한 패러다임을 바꿀필요가 있다고 까지 얘기 하고 있다.
단순한 게시판 서비스와 같이 서비스 모델이 상당히 간단하고 명확할 경우 기존의 컨트롤러에서 관리자용 액션메서드를 구현해서 사용하면 된다. 그리고 규모가 큰 서비스의 경우 기존의 Controllers, Models, Views 폴더에 적절히 하위 폴더를 생성하고 서비스를 구성할 수도 있다. 그리고 라우터만 잘 설정하면 URI 구조로도 영역을 구분지어둘 수 있다.
그런데, 쇼핑몰이나 복합적인 서비스들(비록 각 서비스 규모가 작을지라도)로 구성된 서비스를 구성할 경우 몇가지 고민이 들게된다.
“MVC 필터를 사용하여 접근 제한 하는건 알겠는데… 그래도 뭔가 찜찜하다!”
“사용자가 조회하는 회원 목록과 관리자가 조회하는 회원 목록은 각기 사용하는 데이터소스가 다르다!”
“Member 컨트롤러 List 액션메서드에 if문 사용하기는 거시기 하고… 그렇다고 회원 목록을 가져오는건데, 사용자는 List액션메서드를 사용하고, 관리자는 뭐… List2 액션메서드 쓰까?”
“액션메서드에 일일이 Authorize 필터를 달아줘야 하다니! 그냥 컨트롤러에 Authorize 달면 좋잖아!”
“여차해서 서비스 한 부분이 뚝! 떨어져 별도의 서비스로 독립되어 서비스 해야한다면… 이거…뭐…!!”“아~~~ MVC에서 관리자 페이지 구성하려니 여전히 찜찜하네…”
익숙하지 않은 패턴때문일수도 있지만, 사실 규모가 커질경우 이러한 문제가 현실이 되어버린다. 쇼핑몰에서 관리자용 서비스를 따로 때여 별도의 도메인으로 서비스를 하게 될 경우, 각 컨트롤러의 관리자용 액션메서드들을 따로 추출해서 재구성해야하는 문제가 생긴다. (도메인 모델은 통상 별도의 프로젝트로 분리하는것으로 본다.)
이렇게 서비스의 구분을 필요로 하는 경우 ASP.NET MVC에서 제공하는 Areas(영역)를 활용할 수 있다.

서비스에 영역을 추가할 경우 Areas 폴더가 생성되고 하위에 지정한 이름의 새 영역 폴더가 만들어진다. 그리고 그 하위에는 별도의 MVC 고유의 폴더인 Controllers, Models 그리고 Views폴더가 생성된것을 확인할 수 있다.
이렇게 추가된 영역은 global.asax에 보면 AreaRegistration.RegisterAllAreas();에 의해 서비스 시작시 등록된다.
새로 만든 영역의 접근은 기본적인 라우트규칙에서 /[영역이름]/{controller}/{action}/{id} 와 같은 구조를 가진다. 따라서 새로운 영역으로 이름이 Admin이라는 영역을 생성했다면, 회원 목록을 조회할때 통상… http://[도메인]/Admin/Member 와 같이 접근이 가능하다. 물론 사용자가 회원 목록을 조회하고자 한다면 http://[도메인]/Member 와 같이 접근하면 된다.
이렇게 영역을 구분할 경우, 각 영역별 컨트롤러의 네임스페이스가 서로 다르게 되므로…
같은 이름의 컨트롤러를 각 영역별로 작성이 가능해진다. 따라서 물리적으로 서비스별 컨트롤러를 구분할 수 있다.
물론 모델도 영역별로 구분가능하다. 도메인 모델을 별도의 프로젝트로 빌드한다 해도, ViewModels 패턴을 적용한 모델의 경우 각 영역별 모델에 위치시켜두고 관리할 수도 있겠다.
굳이 영역을 사용하지 않아도 이러한 구분은 네임스페이스 구분과 적절한 하위폴더 관리로도 가능하겠지만, 컨트롤러, 모델 그리고 뷰가 각각 관리되어 서비스 단위별로 관리해야할 필요가 큰 경우 이러한 영역을 활용하면 도움이 되지 않을까 싶다.
9월 16일, IE9 베타 출시
아직 정식 버전은 아니지만, IE9 베타 버전이 16일 발표되었다. 16일 저녁에 이 소식을 접하고 다음날인 오늘 이시간 까지 이래 저래 만지작 거려보고 있었다. IE9 베타는 Internet Explorer 9 베타용 언어 다운로드 사이트에서 받아 설치해볼 수 있다. 웹 개발자라면 MSDN의 IE 페이지도 확인해보자.
참고로 IE9 프로모션…
IE9 Pinned Sites(사이트 고정) 기능 활용법
IE9 베타 버전에서 브라우저 기능중에 눈에 띈것이 있다면 “Pinned Sites”라는 “사이트 고정” 기능이었다. 이 기능은 Windows 7 운영체제가 가지고 있는 “작업 표시줄에 고정” 기능과 함께 사용자에게 매우 편리한 기능을 제공해 주고 있다.
일반적으로 자주 방문하게되는 사이트의 경우 즐겨찾기에 등록하거나, 주소창의 파비콘을 바탕화면이나…
