Wednesday, May 9, 2018

Sitecore Buckets and Improving bulk import speed

Hello.

I ran into a problem while trying to bulk insert items into a bucket in code, using the Glass Mapper SitecoreService - insert speed was very slow (10,000 items took 50 minutes). Deleting all items from the bucket was also fairly slow.

The solution in code is basically the same as the Powershell equivalent in the link above: -

            using (new Sitecore.Data.BulkUpdateContext())
            using (new SecurityDisabler()) // yes yes I know

            {
                foreach (var item in data)
                {
                    sitecoreService.Create(folderItem, item, false, true);
                }
            }

Yes I know I should be using a UserSwitcher instead of disabling all security..

Total import time down from 90 minutes to 6. Not bad :)


Wednesday, April 11, 2018

SIFLess for easy Sitecore 9 installation

Lazy me, here's a link to an article: -

http://www.rockpapersitecore.com/2017/10/introducing-sif-less-for-easy-sitecore-9-installation/

h/t to RockPaperSitecore for this. Works well, by the way :)

Thursday, January 12, 2017

CodeDen's - Sitecore Habitat Transformer

What is Sitecore Habitat ? 

It's an example implementation of the Helix design principles and conventions for a Sitecore project.

Dennis Lee has thoughtfully provided a nice and safe means of accelerating the process of re-implementing Habitat (the demo) in your own project.

Get it here


Thursday, October 6, 2016

RCS

This is the TLA for a new venture which I, as senior Website-Building Fromage to the Gentry, have been asked to make.

My brief was to make it a) simple to edit and b) editable by simples, so I decided to re-visit my own CMS with a view to "getting this done" (TM). (PS Sorry Sitecore, but you're not going to stoop to this end of the market or their budgetary constraints !)



So as I've basically copy-paste inherited my CMS into hmmm 5 or 6 websites now, with the usual "it deviates here" shenanigans that you get when copying and pasting an entire Visual Studio solution and databases for re-use.

I've done some more sensible stuff, including modularisificating my CMS framework into, let's see now, actual modules (the CMS and its base stuff is now in its own assembly, yay !)


I've studied Bootstrap and the whole "semantic HTML" concept quite a lot at PING Works, mainly as a side-effect of being asked to POC Sitecore Habitat. I'm also trying to do this in my own CMS; with the objective being to understand semantic HTML, modularity and re-skinnability as central tenets to both Sitecore builds (i.e. high-end clients) and my own (i.e. not so).



What in the hell am I talking about ? Dunno mang, watch this space as I reveal all ! Or just a bit. Or maybe nothing. More to follow. Maybe.






Friday, May 6, 2016

Theatre on the River - reskin

So,

My sister runs a very successful drama teaching company in London, and her current site is nice but it uses device detection (51degrees.mobi if you're interested) for mobile/tablet which is no longer the cool way to handle mobile traffic (as now enforced by our Google overlords).

Instead, sites must be responsive which is hardly a term new to anyone reading this drivel but for those of you who've been living in a cave eating beetle dung for the last few years, "responsive" is an HTML 5-based concept where the site's presentation layer (the HTML and CSS) must adapt to the device the site is rendering into.

This means no cop-out of sniffing the device server-side and redirecting to a /mobile set of layouts/masterpages/etc but instead - there is just one site to rule them all. Which is done using CSS media queries.

This isn't the first responsive site I've worked on, ohnono. My brother runs a very successful glass machinery oligarchy in Rugby (home of the game, Rugby fans!) and his site was the first independent site I rebuilt in a responsive style.

Glass Machinery Solutions Ltd














As you can see, it looks good on

a) desktop,
b) tablet, and of course
c) mobile

All served from just the one URL and this pleases our Google overlords, who will continue to rank your site in accordance with their holy scriptures. And not penalise you for redirecting to effectively another site (or worse, your site is not mobile-friendly at all and therefore you fail at everything).

I'm no front-end guru; never have been, don't want to be either. It'll push other, more useful stuff out of my head such as being able to order a beer, or pulling a wheelie between lanes of traffic. What I am good at doing with front-end stuff is glueing someone else's efforts together into an ASP.NET application.

I do understand media queries, they're not hard. I do not however have the necessary skill/patience to make sure the resulting CSS & HTML functions across all browsers, devices and small hairy marsupials.

So ! HTML5Up to the rescue. These guys offer a bunch of free, responsive skins (a bunch of CSS, JavaScript and sample HTML pages) for you to use, as long as you acknowledge their awesome efforts (no probs there guys!).

They also have even more elaborate skins you have to pay for, not a lot, but whether you choose to go the free or paid route, I can highly recommend their work.

The new site needs some image work and polishing (so don't mail me about images being different heights or whatever COS YEAH I KNOW) but I'm sure you'll agree, it looks better. For minimal outlay.

People dedicating their lives to making awesome HTML 5 skins - you have my utmost respect and infinite gratitude.

BTW please don't spam my sister's email using the Contact Us form.

BTTW I'll know who did it and I will find you

BTTTW Look at me. I'm the captain now.

:D

Thursday, June 4, 2015

Be careful what you delete for II - Judgment Day

A while back I posted this regarding deleting data from Sitecore's Analytics database.

TL;DR - Sitecore 6.4 (and possibly other versions using SQL-based analytics) do not want you to truncate the IPOwner table. Analytics stops working.



Lo and behold, we discover today that one of our Sitecore applications hasn't been collecting analytics for a while. Investigation of the database revealed - gasp - no data in the IPOwner table !!!

The Sitecore logs revealed a multitude of Foreign Key constraint violations; specifically the foreign key IP.IPOwnerID - IPOwner.IPOwnerID. It's trying to add a row to the IP table using an IPOwnerID that does not exist.

Wait, what ? There aren't any rows in IPOwner ?

Suspecting magic GUID foul play and thinking that Sitecore is perhaps expecting a row to be there, we crack open a fresh copy of Sitecore 6.4's OMS database and sure enough, hiding in there was this little fella: -

IpOwnerID Name Country VisitorIdentification ExternalUser
9F28FB1A-BDDA-4CE6-9521-D7E6C6D8BB9D (Pending) (Pending) 0


And it all makes sense. While waiting for MaxMind GeoIP service to return something, Sitecore is using this guy as the IPOwner for a new session until the queued service lookup completes (hopefully), so it can write some meaningful location data to the IP record.

Added this row.. tested the site.. analytics starts recording stuff !

So now we know. Delete all the IPOwner rows if you want, just make sure this row finds its way back in.

Tuesday, March 19, 2013

SQL Row_Number() OVER and how to cure indeterminate sort order

There's been plenty of wibbling on Stack Overflow about how paging and sorting at SQL Server level using the Row_Number() function can produce records ordered unpredictably between repeated executions of the same query; perhaps you wouldn't normally do this, perhaps you would and as required by my scenario, I did.

And saw it myself - the variations weren't great but they were there and that's annoying.

I checked and double-checked my stored procedure - it was doing what it was supposed to.

Came across this via Stack Overflow - http://msdn.microsoft.com/en-us/library/ms186734.aspx - and I'll repeat the most pertinent part here: -

There is no guarantee that the rows returned by a query using ROW_NUMBER() will be ordered exactly the same with each execution unless the following conditions are true: -

  • Values of the partitioned column are unique.
  • Values of the ORDER BY columns are unique.
  • Combinations of values of the partition column and ORDER BY columns are unique.

How to fix this ?

So - Microsoft themselves admit this is a problem. There is of course a solution, and mine was a table variable.

DECLARE @IDTable TABLE
(
  InvoiceID INT,
  RowNumber BIGINT,
  TotalRowCount BIGINT
)


Because the actual results my query returns contained a number of identical field values in many cases (same Customer Name, same Status.. etc) the three rules outlined in the MSDN article weren't being met; hence the problem. You can guarantee the order by stripping the columns returned from the actual query back to a bare minimum - a key field (InvoiceID in my example), the RowNumber itself and (optionally) the result of the partition column expression.

Having defined our table, I then run the complete query but returning only those 3 fields needed: -

INSERT INTO @IDTable (InvoiceID, RowNumber, TotalRowCount)
 SELECT * FROM
 (
  SELECT DISTINCT i.InvoiceID,
  Row_Number() OVER
  (
   ORDER BY
   CASE
    WHEN @SortExpression = 'DateCreated'  
     THEN  i.DateCreated
   END ASC,

    -- snip - long list of fields to order by

  ) AS RowNum,
  COUNT(*) OVER(PARTITION BY 1) as TotalRowCount 

  FROM ..
   
  WHERE ..

 ) results

 WHERE RowNum 
                BETWEEN (@PageIndex * @PageSize + 1) 
                AND     ((@PageIndex * @PageSize) + @PageSize)
 ORDER BY RowNum

That deals with correct ordering of results; now run another query to join back to @IDTable, this time also retrieving all the coulmns needed: -

 SELECT i.InvoiceID, c.CustomerName, .. etc
 FROM @IDTable idt
  INNER JOIN Invoice i WITH (NOLOCK) ON idt.InvoiceID = i.InvoiceID
  INNER JOIN Customer c ..

    -- and so on 



Ok so there's some overhead involved with running two queries as opposed to one; but the execution time is still fast and it gives me exactly what I want. You should have seen what was happening before..

Wednesday, March 6, 2013

Sitecore - Illegal characters in path

Meh.


        /// checks for the existence of illegal characters in the URL, these being " < > | and SPACE
        /// for some reason Sitecore's Sitecore.Pipelines.PreprocessRequest.FilterUrlExtensions pipeline
        /// component doesn't handle the exception
        ///
        /// FilterUrlExtensions.Process > Sitecore.Web.RequestFilter.Process > RequestFilter.IsBlocked > RequestFilter.GetExtension
        ///     > System.IO.Path.GetExtension > System.IO.Path.CheckInvalidPathChars = where it goes bang, and isn't handled
        ///  
        /// This component fixes this behaviour by stripping illegal chars and then re-writing the URL using HttpContext.RewritePath
        ///
        /// Note that for some reason, if IIS Static File Compression is enabled for the site, this can cause resources to
        /// be returned to the browser with the correct encoding type (GZIP) but the browser fails to parse them
        ///
        /// Don't know why this is
        ///
        /// For this reason, the URL is only rewritten if the request URL contains these illegal chars


Let's just say this has caused problems with custom pipeline components I have worked on.

Friday, May 11, 2012

Diagnosis vs Design

This is a subject close to my heart, and my abilities too.

I am ok at diagnosis, but by no means a ninja - and sometimes as a solution architect you're going to need to diagnose a problem. Some are easy. Some are not.

I am from the design school of systems architecture which means doing it right in the first place. I can hold court on OO design til the cows come home, and I can design you a domain model that will model your system's data and - most importantly - its behaviour. Cleanly, concisely and to the point of your requirement.

I can do this across a huge range of technologies - web front end, Sitecore, Kentico, WCF, ServiceStack, Biztalk, MSMQ, SQL, Oracle. The building blocks of a lot of .NET solutions.

What I'm not so good at - compared to the above - is diagnosing where and how things are going wrong in an existing solution. I tip my hat to a select few who can do this better than me.

I can diagnose things that are wrong to a certain degree, and you can and should too. Today I had to sort out a non-working ASP.NET app. Here's what I did, in 10 minutes: -

  • Installed the IIS6 metabase compatibility components
  • Ran aspnet_regiis /i to install ASP.NET
  • Set the App Pool's identity to Local System
  • Set permissions on folders
This got the guy going, and I revelled slightly in my ability to spot what was wrong and correct it.

But problems I've seen in the past (and recently) have been a whole lot hairier and have required more tools, and more knowledge.
  • .NET Reflector - this thing is a life saver in so many situations. What does this DLL do ? Let's find out..
  • SQL Profiler - now this one I do know well. What's being sent to the server ? Answer in confidence. SQL Profiler is your friend
  • ANTS Memory Profiler - nice, but a bit bloated. Handy, but the daddy is...
  • WinDebug - see the Matrix as it truly is. Here is where your memory is being allocated. And why
I'll tip my hat to some colleagues of mine past and present who can do this diagnosis phase of systems engineering way better and quicker than I can: -

A guy I knew at Datacom who showed me The Way regarding debugging a live app using WinDebug

Another guy at Datacom who worked for me on an app of unfathomable architecture, and is the only person I know to date who got WS-AT working over MS DTC. We spent a pleasant evening, actually entire day and  night, debugging a WCF sync service that if I'd had more say would never have existed.. another story ..

A person I work for now, who can cut razor-like to the cause of and solution to many problems using the weapons in his arsenal, which are mostly Reflector but he also brings his networking knowledge to bear on things I don't get. Like Nick, an awesome troubleshooter

Working with all of these dudes has humbled me when finding problems. These guys can pick the bones of a solution to the n-th degree and tell me what is and isn't right.

I can do this to a degree, but I think I have a different mindset. I design. I usually do it well.

Both skillsets are needed when doing big stuff. 

Which are you ? Can you do both at the outer edges of ability ? I'm not sure anyone can.




Be careful what you delete for...

Be very careful deleting stuff from a Sitecore Analytics database before you hand it over to your hosting company.

The usual approach is to clear the Page, PageEvent and Session tables because these will be full - many months worth of team dev, in my case - of locally registered page views. BUT - Don't get overzealous and delete the contents of the IPOwner table.

 Sitecore Analytics refused to do anything when I did this. Required inspection of the logs, which revealed a problem with some record not actually existing in IPOwner, even though a cursory inspection showed that this logged unique IP's for a session.

Delete these with extreme caution... because analytics stops working, period !

Tuesday, April 17, 2012

Javascript Frameworks

Richard sent this list of Javascript / Web frameworks around the office today.

The stupendously named batman.js caught my eye, as I am prone to following daft names, although my knowledge of Coffeescript is not great (I've had a play).

However, the even more daftly named Mustache and it's big brother Handlebar.js (where do people get these names from ?) are more my thing and I am playing with them now. More to follow.

Initial Thoughts on RavenDB

It's good.

More to follow :)



But seriously, it's good and I'm scratching around the surface at the moment. I prefer its simpler-use approach to document (i.e. object) storage compared to Mongo. I like the fact it's natively JSON and I can serialize an entire object graph in one go.

I like the fact that I can embed it it an application. As in - embed the server.

Rumour is that Sitecore 7.0 is going to support it. Interesting. We at 5 Limes may find a use for it sooner rather than later.

And I'm currently exploring the way it implements Map/Reduce indexes

More to follow. Much more probably - this is my R & D subject at work, and I have to present it next week. Wonder if I can use it to present CQRS to the team in my next project ?

Friday, February 10, 2012

nHibernate 3.0 DateTimes

Ok, so - this sucks.

I want to be able to filter date ranges in an NH 3.0 query, and I don't want to have to first scan the whole table and then feed the results through LINQ to get my range. This table could contain millions of rows and my query will be hit on each request of a certain page.

This works: -

public IList InvoiceSearch(InvoiceSearchCriteria criteria)
        {
            IList list = null;

            using (var session = SessionFactory.OpenSession())
            {
                var query = session.QueryOver < Invoice > ();

                if (criteria.DateCreatedFrom != null)
                {
                    query = query.AndRestrictionOn(x => x.DateCreated).IsBetween(criteria.DateCreatedFrom.Value).And(criteria.DateCreatedTo.Value);
                }

  .. snip



What I'm doing here is filtering my results by a date range using nHibernate.Criterion.Lambda.IQueryOverRestrictionBuilder's innate ability to write good SQL and therefore limit the set I am querying. So far so good.

I have a requirement to show all thingos (Invoices in this case) in a given month and year. According to those that know, this is simply (?) solved by mapping a YEAR and MONTH function to my Fluent entity, and then querying by those properties in my QueryOver expression, thusly: -

Entity: -

        public virtual int InvoiceYear     { get; set; }
        public virtual int InvoiceMonth    { get; set; }

        Map: -

        Map(x => x.InvoiceYear).Formula("SELECT YEAR(DateCreated)");
        Map(x => x.InvoiceMonth).Formula("SELECT MONTH(DateCreated)");     


and then doing suchlike in my querying codeage: -

    list = session.QueryOver()
                  .Where(x => x.InvoiceYear == year)
                  .And(x => x.InvoiceMonth == month)
                  .List();




Well - it doesn't work. nHibernate throws a fit when trying to compile the query expression.

In the end, the only workable solution - one that doesn't involve a complete table scan into NH entities and from there a filter, cos that'll be scalable !!! - is as follows: -

using (var session = SessionFactory.OpenSession())
            {
                DateTime from = new DateTime(year, month, 1, 0, 0, 0),
                         to   = new DateTime(year, month, DateTime.DaysInMonth(year, month), 23, 59, 59);

                list = session.QueryOver < Invoice > ()
                              .Where(x => x.Active == true)
                              .AndRestrictionOn(x => x.DateCreated).IsBetween(from).And(to)
                              .List();

            }


Got to say that annoys me somewhat, but then I guess I need to spend more time studying expression trees. On the other hand, I have business requirements that a) need solving and b) must scale to meet demand.

Wish me luck :)

Monday, August 29, 2011

Fun with Helicon Ape

Minor inconvenience with someone changing a Sitecore item name (and thus URL) today; this led to stale links from Google, which had not yet re-indexed the new URLs.

Bit of a fail because we should have put a 301 redirect in place to handle this, which leads me on to the reason for this post. Try as I might I couldn't find a simple example for using the IIS7 Helicon Ape plug-in to map the start of a URL/* to a new URL/*, for example: -

/products/old-category/*
i.e. /products/old-category/sub-category/thing/whatever

to

/products/new-category/*
i.e. /products/new-category/sub-category/thing/whatever


Helicon Ape uses regular expressions to process requests and do the actual redirection at IIS level. Turns out what I needed was to append a $1 (being the first and in this case only parameter to the rewrite expression) to the end of the target URL: -

RewriteRule ^products/old-category/(.*)$   http://www.mysite.com/products/new-category/$1   [NC,R=301,L]

Job done. Works nicely :)

Wednesday, December 1, 2010

Speeding Up NHibernate Startup Time

Thanks to Development With A Dot for this one. Works a treat :)

jQuery "Live" and ASP.NET Ajax

Now as you may be aware, ASP.NET Ajax is a big bloaty beast and I don't like it much. I wouldn't recommend it for a public website taking a zillion hits a day.

However, for knocking together a UI for an intranet app, where you have gallons of network capacity (or is it measured in fathoms, can't remember) and can dictate the browser that will be used, it's not so bad - mainly cos it does a lot for little input. Which I do like.

I have a scenario in an intranet app where I want to show a modal popup after validating certain conditions in server-side code; and within this popup, I want to do some jQuery Ajax.

Following the quick'n'dirty approach, I used an AjaxControlToolkit ModalPopupExtender (no sniggering at the back) to show the popup. All good so far - popup appears and disappears.

Within the popup, I wanted the user to be able to search for products by name & category, and select from a list returned, closing the modal as they do so. Here's the script I was trying to attach: -

$(document).ready(function ()
{
    $("#btnSearch").click(function ()
    {
        $.getJSON("itemSearchHandler.ashx",
                    {
                        "categoryID": $("#cboSearchCategory option:selected").val(),
                        "term": $("#txtSearchProductName").val()
                    },
                    handleResult
                );
    });

    // etc


Trouble is, this handler didn't fire, and I should have guessed as to why not, but h/t to Epic Phil for the solution.

The reason the event doesn't fire is because the DOM elements I am trying to reference don't exist when $(document).ready fires. They are dynamically added by ASP.NET Ajax when the Modal is displayed. jQuery tries to attach a handler and gives up when it can't find the element I'm asking to attach my script to - btnSearch - in the DOM.

Fortunately there is a way around this, and that's to use the .live method: -

$(document).ready(function ()
{
    $("#btnSearch").live('click', function ()
    {
        $.getJSON("itemSearchHandler.ashx",
                    {
                        "categoryID": $("#cboSearchCategory option:selected").val(),
                        "term": $("#txtSearchProductName").val()
                    },
                    handleResult
                );
    });
    // etc

This will attach a handler to the event for all elements which match the current selector, now and in the future.

Handy !

Other thoughts ?
  • ASP.NET 4's ClientIDMode="static" is a life-saver for working with jQuery
  • The standard ASP.NET button control refuses to fire a Click event if it's assigned to the Ok or Cancel controls in a ModalPopupExtender. Use a standard html button (not an input type="button")
  • I feel a bit dirty using ASP.NET Ajax as I've railed against it before and rightly so - here, here and here you go - but as I said, I'm knocking out a fast-turnaround intranet application not an eBay clone
  • That's enough for now

Saturday, November 20, 2010

SiteCore 6.4

SiteCore is a great .NET-based CMS.

I've seen so many CMS's, I'm frankly sick of them. Give me something useful and new. Hello, SiteCore. It's definitely better than Kentico, Umbraco, DotNetPuke, Sharepoint (ok not just a CMS but hey), OBS and even the legendary Pile of Code.

The Online Marketing Suite is the icing in the cake. I was in an OMS training day a few weeks ago and I thoroughly enjoyed it. Instrument your code; watch the marketing people have orgasms. Great stuff.

Thing is, the latest version 6.4 is still not going to *properly* support MVC or .NET 4. MVC I'm still out on; yes I know Model-View-Controller is a great pattern, not sure ASP.NET's version is that good though.

ASP.NET 4 WebForms is superb - please read this if you're not up to speed on the many new features it offers. Most ASP.NET developers are WebForms based, and MVC is a big paradigm shift for them. I know it allows a greater testing surface, amongst other benefits, but if you're doing your job properly and maybe following an MVP pattern in your web applications, then why switch ?

I think SiteCore have a fight on their hands; open-source competitors are going to eat into their share if they can't provide up to date development models, and frankly their licensing takes the piss. They want to charge us for staging licenses. Like.. what ??? Get bent guys..

The real question. Is Orchard the new messiah, or just a very naughty boy ?

Monday, November 8, 2010

nHibernate 3.0 QueryOver - Searching, and Paging at database level

I've been through numerous data-access technologies with .NET - plain old DataReaders & SqlCommands, Code-genned layers around these of various descriptions and varying degrees of horrible, Pile of Code, Typed Datasets, Linq-to-SQL, Lightspeed, LLBLGen Pro, Entity Failwork.

Of all these, nHibernate 3.0 is definitely one of them. No doubt something better is on the horizon and when it looms into view I might take a look. Until then, this is working well for me.

In my humble opinion, NH3 is way better than Linq-to-Fail or infact any other model-based ORF (Object-Relational Failfactory).
  1. The fluent style works well; it's nice to use
  2. It's codegen-able like a crazy thing
  3. It's flexible and very extensible
  4. It performs - the generated SQL is lean and exactly what you need, it's measurably faster than L2S or Lightspeed
  5. It isn't Linq-to-SQL
  6. or a cabbage
NH3 gives us a whole set of functionality missing from 2.1 - functions that wrap typical SQL functions. It also gives us a lot of additional flexibility with lambda's prevalent over magic strings, so refactoring and catching problems at design time is now a doddle.

That said, I'd like a nicer way of doing multi-field searches; maybe by using an Expression<ICriteria> as a parameter to my search method ?

Here is my search method, abbreviated so as not to cause boredom: -

public IList CustomerSearch(CustomerSearchCriteria criteria, int pageIndex, int pageSize)
        {
            IList list = null;

            using (var session = SessionFactory.OpenSession())
            {
                var query = session.QueryOver();

                if (!string.IsNullOrEmpty(criteria.CustomerName))
                {
                    MatchMode matchMode;
                     
                    if (criteria.CustomerSearch == CustomerSearchEnum.StartingWith)
                    {
                        matchMode = MatchMode.Start;
                    }
                    else if ..
                    // etc

                    query = query.WhereRestrictionOn(x => x.CustomerName.ToLower()).IsLike(criteria.CustomerName.ToLower(), matchMode);
                }
                
                if (criteria.ActiveOnly)
                {
                    query = query.And(x => x.Active == true);
                }
                if (!string.IsNullOrEmpty(criteria.Address1))
                {
                    query = query.And(x => x.Address1.ToLower().StartsWith(criteria.Address1.ToLower()));
                }
                // etc

I'm working on a nicer way of doing this, so stay tuned. That said, the above is fairly readable; I'm just appending additional sections to the WHERE clause where needed.

Hmm.. Anyway, one thing NH3 gives us is database-level paging using the SQL Server ROW_NUMBER function, which is efficient (much more efficient than selecting the whole set into memory and doing the paging there..).

list = query.Skip(pageIndex * pageSize)
    .Take(pageSize)
    .OrderBy(x => x.CustomerName).Asc()
    .List();


This is nice, except it *only* returns the rows in the page. How do I know how many rows there are in total, so I can set up my pager accordingly ?

This is easy when done at stored procedure level, as I can just run an additional Count(*) SELECT and return an OUT parameter, all in one hit to the database. Am I going to need two hits here ?

As it turns out, the CriteriaTransformer has a "TransformToRowCount" method, which does what we need. This does issue one more SQL statement for the count, and it's submitted separately - so two round trips, not ideal.

list = query.Skip(pageIndex * pageSize)
    .Take(pageSize)
    .OrderBy(x => x.CustomerName).Asc()
    .List();

int count = CriteriaTransformer.TransformToRowCount(query.UnderlyingCriteria)
                        .UniqueResult();


And after reading this, the process has been simplified even further with the addition of an extension method. So the previous example can be re-written as just ..

list = query.Skip(pageIndex * pageSize)
    .Take(pageSize)
    .OrderBy(x => x.CustomerName).Asc()
    .List();

int count = query.RowCount();


Nice ! But still two database hits. There are two ways to fix this: -
  1. Use a transaction
  2. Use a projection
I'll write some drivel about projections at a later date.

Toodles.

StructureMap usefulness

Yet again I find myself spending literally minutes of my life trying to figure out why something is going wrong, only to find a useful feature of something that could potentially save many more minutes; time better spent writing unit tests, for example, or lolling around the coffee shop talking about stuff.

Turns out in the end I'd configured the wrong repository name. Dumbass mistake of the kind we all make from time to time, but one of those cases where the wood is clouding the view of the trees in the way woods always seem to.

I'd done this ..


instead of this..


Note "EmployeeRepository" instead of "LookupRepository". For clarification, LookupRepository wasn't yet finished.

Upon trying to resolve a reference to IEmployeeService, StructureMap was throwing Exception code 202 - "No Default Instance defined for PluginFamily IEmployeeRepository". Confusing at first, turns out I was of course staring at the obvious problem all along.

Trying to replicate this was a pain as it was a case of fire up unit test; get to point that fails; fail (repeat).

Now I'm sure you've come across the ObjectFactory's "WhatDoIHave" method - which dumps out the state of the container, and what resolves to what. It's a useful way of pinpointing anything that doesn't have a concrete type defined for resolution. However, this chap is even more useful: -


Rather than having to get to the GetInstance method of StructureMap to find out you have a configuration problem, you can instead call this after ObjectFactory.Initialize, and it'll run through the map and attempt to construct everything it can.

I wouldn't recommend this for production release, but while in build it could save you at least one cup of coffee. And that's a good thing.

StructureMap is great. So much nicer to use than Unity. Feeling the love :o)