In this part of our MyBatis.NET series, we’ll go over how to use submaps and discriminators to help us instantiate the right object in an inheritance hierarchy declaratively. As usual, we will be building on the code build in prior parts of this series, and will be using the AdventureWorks database as our target database.

Discounts!

discounts

The AdventureWorks database defines special offers in its SpecialOffers table. These are discounts that are made available based on start and end dates. Some of these discounts are based on a certain quantity being purchased, while others are just flat rates. We want to be able to intelligently load items from this table and have our loaded objects ready to be applied to the purchase we are currently processing. To capture this in our object model, we define an interface for the actual discount application and define a base class to define the common properties all of our discounts will have. The model appears below.

classDiagram

Note that we’ve defined two additional properties on the QuantityDiscount class to capture the needed range for the possible application of our discount.

The Maps

The maps follow a very similar tack to the object hierarchy. We first define a base map, ProductDiscount, that acts as our abstract class.

<resultMap id="ProductDiscountBaseRM"
           class="ProductDiscount">
    <result property="Id" column="SpecialOfferId"/>
    <result property="Description" column="Description"/>
    <result property="DiscountPercentage" column="DiscountPct"/>
    <result property="StartDate" column="StartDate"/>
    <result property="EndDate" column="EndDate"/>
</resultMap>

Next we define the maps for our inherited classes, namely FlatDiscount and QuantityDiscount.

<resultMap id="FlatDiscountRM"
           class="FlatDiscount"
           extends="ProductDiscountRM">

</resultMap>

<resultMap id="QuantityDiscountRM"
           class="QuantityDiscount"
           extends="ProductDiscountRM">
    <result property="LowQuantity" column="MinQty"/>
    <result property="HighQuantity" column="MaxQty"/>
</resultMap>

The first thing to note is the use of the extends attribute in the resultMap element. This tells MyBatis.NET to incorporate the contents of the map we are extending into this one. We will be defining the ProductDiscountRM below. Also note that on the FlatDiscountRM result map, we don’t define any additional result columns due to the fact that the map we are extending will already define everything we need. The QuantityDiscountRM defines the two additional needed properties to properly process a quantity discount, namely the minimum and maximum number of units in which the discount applies.

Now we get to the map that ties everything together, the ProductDiscountRM map.

<resultMap id="ProductDiscountRM"
           class="ProductDiscount"
           extends="ProductDiscountBaseRM">
    <discriminator column="Type" type="string"/>
    <subMap value="DiscontinuedProduct" resultMapping="FlatDiscountRM"/>
    <subMap value="ExcessInventory" resultMapping="FlatDiscountRM"/>
    <subMap value="New Product" resultMapping="FlatDiscountRM"/>
    <subMap value="No Discount" resultMapping="FlatDiscountRM"/>
    <subMap value="Seasonal Discount" resultMapping="FlatDiscountRM"/>
    <subMap value="Volume Discount" resultMapping="QuantityDiscountRM"/>
</resultMap>

Good stuff here. The map extends our base product discount map, which gives the other maps that extend off of this one the needed base fields. The presence of the discriminator element tells MyBatis.NET that we are going to do different things based on the associated column, in this case the Type column on the SpecialOffer table. The following subMap elements indicate what exactly we are going to do based on the different values of the Type column. More precisely, these elements tell MyBatis.NET what mappings to use when a row is encountered with the corresponding Type value. So from the above map, we see that every type of special offer is a flat discount, save for our volume discounts, which use the logic captured in the QuantityDiscount class.

The final map we need is the one that defines the select statement that will be used to hydrate our discount hierarchy. This statement map appears below.

<select id="GetProductDiscount"
        resultMap="ProductDiscountRM"
        parameterClass="int">
    select
        *
    from
        Sales.SpecialOffer
    where
        SpecialOfferID = #value#
</select>

The Tests

We’re going to gimmick up our test based on the data so we can demonstrate the above maps working. Based on the data below, we can see that the discount with ids 2-6 are volume discounts. We will load a volume discount and a non-volume discount to demonstrate correct operation.

specialOfferData

So we’ll load the discounts with ids 5 and 7. The code is below.

[Test]
public void GetDiscountTest()
{
    decimal priceAfterDiscount;
    decimal fakePrice = 10M;
    int fakeUnitsOutOfRange = 14;
    int fakeUnitsInRange = 50;

    // validate right type are loaded based on discriminator
    IProductDiscount qDiscount =
        (IProductDiscount)Mappers.AWMapper.QueryForObject("GetProductDiscount", 5);
    Assert.That(qDiscount, Is.TypeOf<QuantityDiscount>());
    // validate correct discount based on unit count
    priceAfterDiscount = qDiscount.ApplyDiscount(fakePrice, fakeUnitsInRange);
    Assert.That(priceAfterDiscount, Is.LessThan(fakePrice));
    priceAfterDiscount = qDiscount.ApplyDiscount(fakePrice, fakeUnitsOutOfRange);
    Assert.That(priceAfterDiscount, Is.EqualTo(fakePrice));

    // validate right type is loaded based on discriminator
    IProductDiscount fDiscount =
        (IProductDiscount)Mappers.AWMapper.QueryForObject("GetProductDiscount", 7);
    Assert.That(fDiscount, Is.TypeOf<FlatDiscount>());
    // validate that correct discount is applied
    priceAfterDiscount = fDiscount.ApplyDiscount(fakePrice, 1);
    Assert.That(priceAfterDiscount, Is.LessThan(fakePrice));
}

I usually like to break my tests up a bit more than this, but since we’re focusing on the mapping side of things, I left it all in one test. Enjoy!

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

   
© 2011 Musings of the Bare Bones Coder Suffusion theme by Sayontan Sinha