2009-02-28 5 views
5

저는 프로젝트 데이터 저장소에 객체 매핑을 캡슐화하려고했습니다. 어쩌면 EF는 필요한 추상화 수준을 제공 할 것이지만 지금 Linq를 SQL로 사용하고있는 여러 가지 이유에서 볼 수 있습니다. 나는 이후가 MapResource 식을 호출 할 수있는 코드는 실패합니다Linq를 SQL 데이터 액세스에 캡슐화하는 가장 좋은 방법은 무엇입니까?

public List<ModUser> GetUsers() { 
    Users.Select(MapUser).ToList(); 
} 

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     return u => new ModUser() { 
      UserId = u.User_Id, 
      UserResources = u.Resources(MapResource) 
     } 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... 

: 다음 코드는 ModUser 저장소가 노출하는 POCO입니다 ModUser 개체의 목록으로 데이터베이스에 사용자를 반환하는 것을 목표로 다른 식에서 그것을 호출하려고합니다. 나는 이것을 "MapResource"를 u => 새로운 ModResource()로 대체 한 다음 ExpressionVisitor를 사용하여이 자리 표시 자 노드를 찾아 MapResource 표현식으로 대체함으로써이 문제를 해결했습니다.

ModUser의 속성에 단일 속성, 즉 UserResource = MapResource와 관련된 표현식을 지정하려고하면 비슷한 문제가 발생합니다. Expression 클래스의 메서드를 사용하여 필요한 식을 수동으로 결합하여이 두 번째 문제를 해결할 수있었습니다.

나는 위의 코드를 변경할 수 있음을 깨닫는다 다음

UserResources = u.Resources(r => MapResource.Compile().Invoke(r)); 

그러나에 우리가 있기 때문에, MapResouce에 필요한 모든 R의 속성이 아니라 사람을 얻을 필요가 생성되는 최종 SQL 쿼리 지금은 함수를 다루고 있습니다. 또한 MapResouce가 표현식이 아닌 함수로 사용되기 때문에 더 이상 테이블에 액세스 할 수 없으면 불가능합니다. DeferredLoadingEnabled를 true로 설정할 수 있지만 필요한 모든 테이블과 조인하도록 기본 쿼리를 수정하는 대신 개별 쿼리가 여러 개 생성됩니다.

이 작업이 .NET의 이후 버전에서 더 쉽게 될 것인지 아니면 내가 잘못된 방향으로 가고 있는지 아는 사람이 있습니까? Linq 및 Expression 기능이 정말 마음에 듭니다. 더 읽기 쉬운 코드를 사용하여 구현할 수 있기를 바랍니다.

업데이트

생각 나는 표현이 더 작성 가능 만든 방법에 대한 몇 가지 사례를 추가 할 수 있습니다. 그들은 간결하지는 않지만 일을 끝내게됩니다.

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     Expression<Func<User, ModUser>> mapUser = u => new ModUser() { 
      UserId = u.User_Id, 
      UserResources = u.Resources(r => new ModResource()) 
     }; 
     return mapUser.MapResources(this); 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... } 


public static Expression<Func<T0, T1>> MapResources<T0, T1>(this Expression<Func<T0, T1>> exp, DataContext dc) { 
    return exp.Visit<MethodCallExpression, Expression<Func<T0, T1>>>(m => { 
     if(m.Arguments.Count > 1 && m.Arguments[1].Type == typeof(Func<DataContext.Resource, ModResource>)) { //Find a select statement that has the sub expression as an argument 
      //The resource mapping expression will require the Resource object, which is obtained here 
      ParameterExpression resourceParam = ((LambdaExpression)m.Arguments[1]).Parameters[0]; 
      return Expression.Call(m.Method, m.Arguments[0], //The first argument is the record selection for the 'select' method 
       Expression.Lambda<Func<DataContext.Resource, ModResource>>(//Provide the proper mapping expression as the projection for the 'select' method 
        Expression.Invoke(dc.MapResource, resourceParam), 
        resourceParam) 
       ); 
     } 
     return m; 
    }); 
} 

여기 내가 뭘하고 있니? 이 버전의 MapUser에서는 ModResource 객체를 올바르게 만들지 않으며, 그냥 더미 버전을 만듭니다. 그런 다음 모호한 호출을 찾아 원래의 호출로 대체하는 표현식 방문자 메서드를 호출합니다. 나에게 원래 표현식 트리를 기본적으로 구성 할 수 있으므로 표현 구문이 부족한 것 같지만 트리를 실제로 실행해야한다. 아래는 단일 사건을 다루는 내가 찾은 또 다른 해결 방법입니다 :

public Expression<Func<User, ModUser>> MapUser { 
    get { 
     Expression<Func<User, ModResource, ModUser>> mapUser = (u, resource) => new ModUser() { 
      UserId = u.User_Id, 
      UserResource = resource; 
     } 

     return mapUser.CollapseArgument(MapResource, user => user.MainResource); 
    } 
} 

public Expression<Func<Resource, ModResource>> MapResource { ... } 

public static Expression<Func<T0, T3>> CollapseArgument<T0, T1, T2, T3>(this Expression<Func<T0, T1, T3>> exp, Expression<Func<T2, T1>> exp0, Expression<Func<T0, T2>> exp1) { 
    var param0 = Expression.Parameter(typeof(T0), "p0"); 
    var argExp = Expression.Invoke(exp0, Expression.Invoke(exp1, param0)); 
    return Expression.Lambda<Func<T0, T3>>(
     Expression.Invoke(exp, param0, argExp), 
     param0); 
} 

내가 사용자 데이터에서 자원 데이터를 얻을 수 있다는 사실을 알고이 두 번째 예에서하지만 난 수 없습니다 "인라인"로 표현 이를 수행하고 리소스 데이터를 리소스 POCO에 매핑하는 방법을 보여줍니다. 하지만 이미 매핑 된 리소스 POCO가 주어진 식 트리를 수동으로 생성하여 사용할 수 있습니다. 그런 다음 사용자로부터 자원 원시 데이터를 얻는 방법과 원시 자원 데이터를 자원 POCO로 맵핑하는 방법을 보여주는 최종 표현식을 표시하는 다른 표현식을 작성할 수 있습니다. 이제는이 모든 정보를 하나의 표현 트리로 결합하여 자원 별 매개 변수를 "축소"하는 방식으로 생각할 수 있습니다. 기본 사용자 매개 변수에서 얻을 수 있기 때문입니다. 이것은 위의 코드가하는 것입니다.

그래서 표현식을 매우 구성 가능하게 만드는 방법을 찾았습니다 ...그것은 단지 깨끗하다고 ​​느끼지 않습니다.

답변

0

나는 당신이 POCOs를 사용하기를 원한다면 SQL에 Linq가 최선의 선택이 아니라고 생각한다. 아마 당신은 NHibernate 같은 것을 사용하는 것이 훨씬 낫다고 생각합니다. Linq에서 POCO로 SQL을 사용한다는 것은 데이터베이스의 최상위에있는 데이터 계층 (Linq에서 SQL로) 위에 레이어를 구축한다는 것을 의미합니다. NHibernate를 사용하면 모든 코드를 작성하고 곧바로 데이터베이스에 매핑 할 수 있습니다. 적은 레이어 == 적은 코드 == 적은 작업.

+0

@ 크리스가 사용하지 않은 자 NHibernate,하지만 "코드가"xml 파일에 매핑 움직이지 않는다? – eglasius

+0

Fluent NHibernate를 사용하십시오 - 그러면 XML 대신에 모든 리팩토링/컴파일 시간 검증을 통해 코드를 얻을 수 있습니다. –

+0

NHibernate로 이동하는 것이이 프로젝트의 옵션이 아니기 때문에 나는 Linq to SQL 솔루션을 찾고있다. – LaserJesus

1

Linq To SQL이 POCO를 지원하는 방식은 조금 다릅니다.

지속성을 무시하려면 LTS 디자이너가 아닌 modUser 매핑 방법 (열, 연결 등)을 설명하는 매핑 파일을 사용합니다. 새 컨텍스트를 만들 때 XML 매핑 파일을 XMLMappingSource로 전달합니다.

이렇게하면 LTS가 개체를 데이터베이스에서 반환합니다.

IList (T 중) 유형의 읽기/쓰기 속성으로 컬렉션 연결 속성을 정의하면 LinqToSQL에서 해당 컬렉션에 지연로드를 제공하기에 충분하지만 아직 시도하지 않았으므로 그것을 보증 할 수 없습니다.

Entity Framework는 현재 버전의 POCO 지원 (기본적으로 대부분의 사람들이 POCO라는 용어를 이해하는 한 기본적으로 없음)에서 더욱 악화됩니다.

모든 일반적인 LTS 제한이 이에 적용되므로 "값 개체"매핑이 적용되지 않습니다. 조금 더 멀리 데이터베이스와 POCO 지원에서 제거하고자한다면, 당신은 NHibernate를 볼 필요가있다.

+0

매핑 파일은 내가 필요로하는 풍부한 표현력을 가지지 않을 것입니다. 이 작업을 수행하는 것이 상상할 수있는 유일한 방법은 일련의 구성 가능한 표현식을 사용하는 것입니다. 나는 실제로이 작업을 수행하고 예제를 보여주기 위해 게시물을 업데이트하려고합니다. 내가 원하는만큼 깨끗하지는 않습니다. – LaserJesus

1

좋아, 나는 OP의 질문 (멍청한 웃음)을 읽는 것을 끝내지 않았지만 Linq-SQL 특성을 사용하여 POCO 개체를 장식 할 수 있다는 것을 알고 있었습니까? 디자이너를 사용할 필요가 없습니다.

다음은 현재 나에게 열려있는 코드의 임의의 예입니다. 이것은 "제품"이라는 POCO이며 Linq-SQL DataContext와 상호 작용할 수있는 속성이 적용된 POCO입니다. 꽤 오랫동안

HTH

using System; 
using System.Collections.Generic; 
using System.Data.Linq; 
using System.Data.Linq.Mapping; 
using System.Linq; 
using System.Web; 

namespace Redacted.Site.Models.Store 
{ 
    /// <summary> 
    /// A "Product" is a good for purchase at the store. 
    /// </summary> 
    [Table(Name = "s.products")] 
    public partial class Product 
    { 
     /// <summary>Gets or sets the PK of the object/row.</summary> 
     [Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, DbType = "INT NOT NULL")] 
     public Int32 ID { get; set; } 

     /// <summary>Gets or sets the Title.</summary> 
     [Column(Name = "title", DbType = "NVARCHAR(500) NOT NULL")] 
     public String Title { get; set; } 

     /// <summary>Gets or sets the Lede.</summary> 
     [Column(Name = "lede", DbType = "NVARCHAR(MAX) NOT NULL")] 
     public String Lede { get; set; } 

     /// <summary>Gets or sets the Description.</summary> 
     [Column(Name = "description", DbType = "NTEXT NOT NULL")] 
     public String Description { get; set; } 

     /// <summary>Gets or sets the Price.</summary> 
     [Column(Name = "price", DbType = "FLOAT NOT NULL")] 
     public Double Price { get; set; } 

     /// <summary>Gets or sets the FK to the <see cref="Department"/>.</summary> 
     [Column(Name = "department_id", DbType = "TINYINT NOT NULL")] 
     public Byte DepartmentID { get; set; } 

     /// <summary>Gets or sets the date/time the product was released to the store.</summary> 
     [Column(Name = "released_on_utc", DbType = "DATETIME NOT NULL")] 
     public Int32 ReleasedOnUtc { get; set; } 

    } 
} 
+0

이 방법을 사용하여 때로는 사소한 관계를 통해 여러 테이블에서 속성을 파생시키는 POCO를 채울 수 있습니까? 나의 초기 반응은이 접근법이 매우 단순한 매핑에서만 작동한다는 것이다. 보다 복잡한 시나리오의 링크 또는 예가 있습니까? – LaserJesus

+0

예. L2S 디자이너는 코드 생성기 일뿐입니다. 마법은 없습니다.따라서 L2S 디자이너가 자신의 생성기로 자신의 코드에서 수행 할 수있는 모든 작업을 수행 할 수 있습니다. 나는 길을 걷고 있지만 다음 주에 더 복잡한 예를 들어 이것을 개정 할 것입니다. – Portman

+0

제 관심은 제가 디자이너를 통해 원하는 것을 성취 할 수 없었고 표현 방법을 더 많이 찾아야한다는 것입니다. 하지만 일단 기다리면 좀 더 복잡한 예제를 살펴 보겠습니다. 감사합니다. – LaserJesus