Showing posts with label Optimising. Show all posts
Showing posts with label Optimising. Show all posts

Tuesday, 17 April 2012

Using QueryOverProjectionBuilder with QueryOver

We all strive to keep our code DRY and sometimes we may have several QueryOver methods that returns the same DTO. This may then lead us to try and create a method that returns a SelectList to reuse in our queries. This gives us the benefit of only changing one method if for example our DTO changes.

First lets look at the QueryOver method. As you can see I have a method call named GetDtoList()
return Session.QueryOver<InvoiceDto>()
    .SelectList(GetDtoList())
    .TransformUsing(Transformers.AliasToBean<InvoiceDto>())
    .List<InvoiceDto>();

Now for the GetList method. As you can see we return a Func of QueryOverProjectionBuilder. In the code we simply build the list as we normally would and just return it:-
Func<QueryOverProjectionBuilder<InvoiceDto>, 
    QueryOverProjectionBuilder<InvoiceDto>> GetDtoList() {
    InvoiceDto dto = null;
    return list => list
      .Select(w => w.ClientName).WithAlias(() => dto.ClientName)
      .Select(w => w.InvoiceDate).WithAlias(() => dto.InvoiceDate)
      .Select(w => w.InvoiceId).WithAlias(() => dto.InvoiceId);
}
Pretty nice I think although the use case for this may be a little limited. The next challenge is to use this when we join to another entity i.e.
return Session.QueryOver<Invoice>()
    .JoinQueryOver<Client>()
    .SelectList(GetDtoList())
    .TransformUsing(Transformers.AliasToBean<InvoiceDto>())
    .List<InvoiceDto>();


Wednesday, 4 April 2012

SetResultTransformer into an anonymous type

The other day I was looking for a way to shortcut the SetResultTransformer and cast a list to an anonymous type rather than having to hand craft DTO classes with public properties (getters/setters). I stumbled on a GitHub Gist by Buthrakaur (Filip Kinský) that did just this.

Why would we want to do this?
  • We may want to return a few columns from an entity
  • We want to return Json from an MVC view which just gets thrown away
  • We want to cut down the amount of code that just handles redundant DTO's because we want to pass data around

The solution to this is really easy (thanks Filip), first we create an anonymous type inline and then project straight into this using an extension method .ListAs(dto)
//first create our anonymous type DTO
var dto = new { 
    Id = 0L, 
    Source = string.Empty, 
    Destination = string.Empty, 
    Is301 = false
};

//notice the ListAs(dto) extension method
var model = Session.QueryOver<CmsRedirect>()
  .SelectList(s => s
    .Select(x => x.Id).WithAlias(() => dto.Id)
    .Select(x => x.Source).WithAlias(() => dto.Source)
    .Select(x => x.Destination).WithAlias(() => dto.Destination)
    .Select(x => x.Do301).WithAlias(() => dto.Is301)
  )
  .Take(take).Skip(page * pageSize)
  .ListAs(dto);

return Json(new { Total = total, List = model }, 
    JsonRequestBehavior.AllowGet);
The source to the GIST can be found here.
public static class NHibernateExtensions {
  public static IList<TRes> ListAs<TRes>(
      this IQueryOver qry, TRes resultByExample) {

    var ctor = typeof(TRes).GetConstructors().First();

    return qry.UnderlyingCriteria
      .SetResultTransformer(
        Transformers.AliasToBeanConstructor(
         (ConstructorInfo) ctor)
        ).List<TRes>();
  }
}
And the Json that gets returned is:-
{
  "Total":3,
  "List":[
    {"Id":101000,"Source":"clients.aspx",
       "Destination":"portfolio","Is301":true},
    {"Id":101101,"Source":"consultancy.aspx",
       "Destination":"consultancy","Is301":true},
    {"Id":101102,"Source":"contact.aspx",
       "Destination":"contact","Is301":true},
  ]
}
This is perfect for plugging straight into my Kendo UI Grid.

A big thank you to Filip for taking some of the repetiveness out of my day.

Wednesday, 24 August 2011

NHibernate and optimising with lazy='extra'

I came across a stack over flow post today that asked how you could retrieve the count of children from a parent without having to load the entire collection.

The answer is quite simple all you need is to add lazy='extra' on your mappings. Lets put this to the test, for the following domain:-
public class Movie : Entity {
  public virtual string Name { get; set; }
  public virtual string Director { get; set; }
  public virtual IList<Actor> ActorList { get; set; }
}

public class Actor : Entity {
  public virtual string Name { get; set; }
  public virtual string Role { get; set; }
}
The only mapping of importance is the mapping for the movie:-
public class MovieMapping : SubclassMapping<Movie> {
  public MovieMapping() {
    Property(x => x.Name, x => x.NotNullable(true));
    Property(x => x.Director, x => x.NotNullable(true));
    Bag(x => x.ActorList, bag => {
      bag.Key(k => { 
         k.Column(col => col.Name("MovieId"));
         k.NotNullable(true); 
      });
      bag.Cascade(Cascade.All | Cascade.DeleteOrphans);
      bag.BatchSize(10);
    }, action => action.OneToMany());
  }
}
We now have the following code that retrieves a Movie and then the count of all actors:-
var movie = Session.Query<Movie>()
              .Where(w => w.Id == id).SingleOrDefault();
var actorCount = movie.ActorList.Count();
This actually will send two queries to the database:-
-- statement #1
select movie0_.Id          as Id1_,
       movie0_.Name        as Name1_,
       movie0_.Director    as Director1_
from   Movie movie0_
where  movie0_.Id = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */

-- statement #2
SELECT actorlist0_.MovieId as MovieId1_,
       actorlist0_.Id      as Id1_,
       actorlist0_.Id      as Id2_0_,
       actorlist0_.Name    as Name2_0_,
       actorlist0_.Role    as Role2_0_
FROM   ActorRole actorlist0_
WHERE  actorlist0_.MovieId = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */
As you can see this is NOT the intended results we would expect. We have the first SQL query that retrieves a Movie then the second SQL query performs a select * from ActorRole. This means that the second query selects the whole collection and performs the count in memory.

Is there a easy solution that we can use to instruct NHibernate to use a select count(*) from rather than a select * ? If you read the title of this Blog and are still awake, you are probably screaming out lazy='extra'. So how do we use this?

If you are using XML mappings then you would use:-

   ...

If you are using the new mapping by code then you can achieve the same by adding:-
Bag(x => x.ActorList, bag => {
  bag.Key(k => { 
    k.Column(col => col.Name("MovieId"));
    k.NotNullable(true); 
  });
  bag.Cascade(Cascade.All | Cascade.DeleteOrphans);
  bag.BatchSize(10);
  bag.Lazy(CollectionLazy.Extra);
}, action => action.OneToMany());
Now lets look at the SQL that gets sent to the database:-
-- statement #1
select movie0_.Id          as Id1_,
       movie0_.Name        as Name1_,
       movie0_.Director    as Director1_
from   Movie movie0_
where  movie0_.Id = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */

-- statement #2
SELECT count(Id)
FROM   ActorRole
WHERE  MovieId = 'ffffffff-eeee-dddd-cccc-000000000005' /* @p0 */
It should also be noted that it does not matter if you are using HQL, Query, QueryOver or ICriteria to query your data the outcome is exactly the same. Don't you just love it when being able to tweak the mappings so as to optimise your code.