Skip to content

Projection

Umut Ozel edited this page Apr 19, 2017 · 6 revisions

Projection is getting a definitive expression to convert one object to another. We will use it to call IQueryable's Select method. That means we need to drop our MapContext parameter, get real mapping codes for MapContext calls and inject those codes to expression.

BatMap does this for us, but if you register with custom expression, your expression must use MapContext methods (Map and MapToList) for navigation members to let BatMap decide whether to include that navigation and replace the call with an expression. Having said that, you may want to map manually the navigations which you want to include no matter what (even query is not expanded with that).

Here we can see the ways to project a query.

  • You can let BatMap decide which navigation members to include in mapping.
public IQueryable<TOut> ProjectTo<TOut>(IQueryable query, bool checkIncludes = true)

// when you pass false for checkIncludes, BatMap will include every navigation. 
// be careful, this might end with StackOverflowException..
// (if you have two-way navigations anywhere -like Order.OrderDetails and OrderDetail.Order).
config.ProjectTo<CustomerDTO>(context.Customers, false);

  • You can tell BatMap which navigation members to include exclusively.
public IQueryable<TOut> ProjectTo<TIn, TOut>(IQueryable<TIn> query, params Expression<Func<TIn, object>>[] includes)

// this code will include OrderDetails, Product and Supplier in mapping expression.
config.ProjectTo<Order, OrderDTO>(query, o => o.OrderDetails.Select(od => od.Product.Supplier));

  • You can use IncludePath class to define included navigations. It might be useful when dynamically building includes for queries.

IncludePath is a really simple class, it holds the included member and the children IncludePaths for that member. This structure can be visualized as:

Include Path

public IQueryable<TOut> ProjectTo<TOut>(IQueryable query, params IncludePath[] includes)

// we included OrderDetails.Product to our mapping.
config.ProjectTo<OrderDTO>(query, new IncludePath("OrderDetails", new List<IncludePath> { new IncludePath("Product") }));

  • If your query projection code will be called too many times and you're worried that visiting the query with every call will cause a performance loss (probably won't), we have just the thing. You can create your projection expressions without a query and cache for later use (BatMap also uses these methods internally).
public Expression<Func<TIn, TOut>> GetProjector<TIn, TOut>(bool includeNavigations = true)

// we cached the projection query in a private static field.
_projector = config.GetProjector<Order, OrderDTO>(false);

// here how we can use the expression to convert query to dtoQuery.
var dtoQuery = query.Select(_projector);

  • We can get projector with explicitly included navigations using expressions.
public Expression<Func<TIn, TOut>> GetProjector<TIn, TOut>(params Expression<Func<TIn, object>>[] includes)

_projector = config.GetProjector<Order, OrderDTO>(query, o => o.OrderDetails.Select(od => od.Product.Supplier));

var dtoQuery = query.Select(_projector);

  • We can get projector with explicitly included navigations using IncludePaths.
public Expression<Func<TIn, TOut>> GetProjector<TIn, TOut>(params IncludePath[] includes)

_projector = config.GetProjector<Order, OrderDTO>(query, 
    new IncludePath("OrderDetails", new List<IncludePath> { new IncludePath("Product") })
);

var dtoQuery = query.Select(_projector);

Static API

Mapper static class has only ProjectTo methods from above and they are all extension methods 💯

public static IQueryable<TOut> ProjectTo<TOut>(this IQueryable query, bool checkIncludes = true)

public static IQueryable<TOut> ProjectTo<TIn, TOut>(this IQueryable<TIn> query, params Expression<Func<TIn, object>>[] includes)

public static IQueryable<TOut> ProjectTo<TOut>(this IQueryable query, params IncludePath[] includes)