Search code examples
c#nhibernatefluent-nhibernatedomain-driven-designfluent-nhibernate-mapping

Mapping the same entity to different tables


A bit of domain knowledge

I'm writing a POS (Point Of Sales) software which allows to pay goods or to refund them. When paying or refunding, one need to specify which money transfer mean to use: cash, EFT (~=credit card), loyalty card, voucher, etc.

These money transfer means are a finite and known set of values (a kind of enum).

The tricky part is that I need to be able to store a custom subset of these means for both payments and refunds (the two sets may be different) on the POS terminal.

For example:

  • Available payment means: Cash, EFT, Loyalty card, Voucher
  • Available refund means: Cash, Voucher

Current state of implementation

I choose to implement the concept of money transfer mean as follow:

public abstract class MoneyTransferMean : AggregateRoot
{
    public static readonly MoneyTransferMean Cash = new CashMoneyTransferMean();
    public static readonly MoneyTransferMean EFT = new EFTMoneyTransferMean();
    // and so on...

    //abstract method

    public class CashMoneyTransferMean : MoneyTransferMean
    {
        //impl of abstract method
    }

    public class EFTMoneyTransferMean : MoneyTransferMean
    {
        //impl of abstract method
    }

    //and so on...
}

The reason it is not a "plain enum" is that there exists some behavior that is inside these classes. I also had to declare inner classes public (instead of private) in order to reference them in FluentNHibernate mapping (see below).

How it is used

Both the payment and refund means are always stored or retrieved in/from the DB as a set. They are really two distinct sets even though some values inside both sets may be the same.

Use case 1: define a new set of payment/refund means

  • Delete all the existing payment/refund means
  • Insert the new ones

Use case 2: retrieve all the payment/refund means

  • Get a collection of all the stored payment/refund means

Problem

I'm stuck with my current design on the persistence aspect. I'm using NHibernate (with FluentNHibernate to declare class maps) and I can't find a way to map it to some valid DB schema.

I found that it is possible to map a class multiple times using entity-name however I'm not sure that it is possible with subclasses.

What I'm not ready to do is to alter the MoneyTransferMean public API to be able to persist it (for example adding a bool isRefund to differentiate between the two). However adding some private discriminator field or so is ok.

My current mapping:

public sealed class MoneyTransferMeanMap : ClassMap<MoneyTransferMean>
{
    public MoneyTransferMeanMap()
    {
        Id(Entity.Expressions<MoneyTransferMean>.Id);
        DiscriminateSubClassesOnColumn("Type")
            .Not.Nullable();
    }
}

public sealed class CashMoneyTransferMeanMap : SubclassMap<MoneyTransferMean.CashMoneyTransferMean>
{
    public CashMoneyTransferMeanMap()
    {
        DiscriminatorValue("Cash");
    }
}

public sealed class EFTMoneyTransferMeanMap : SubclassMap<MoneyTransferMean.EFTMoneyTransferMean>
{
    public EFTMoneyTransferMeanMap()
    {
        DiscriminatorValue("EFT");
    }
}

//and so on...

This mapping compiles however it only produces 1 table and I'm not able to differentiate between payment/refund when querying this table.

I tried to declare two mappings referencing both MoneyTransferMean with different table and entity-name however this leads me to an exception Duplicate class/entity mapping MoneyTransferMean+CashMoneyTransferMean.

I also tried to duplicate subclass mappings but I'm unable to specify a "parent mapping" which leads me to the same exception as above.

Question

Does a solution exist to persist my current domain entities ?

If not, what would be the smallest refactor I need to perform on my entities to make them persistable with NHibnernate ?


Solution

  • Finally, I decided to solve the problem by duplicating my entity MoneyTransferMean into two entities PaymentMean and RefundMean.

    Although similar in implementation, the distinction between the two entities makes sense in the business and was for me the least worst solution.