7

I have a database relationship as shown below. The domain objects are created based on LINQ to SQL ORM.

A payment comprises of Cash Payment and Gift Coupon Payments. Suppose the total amount of purchase is 550. It can be paid as following components

1 Gift Coupon Valued 300

1 Gift Coupon Valued 200

I Cash Currency Valued 50

enter image description here

I am inserting new payment records using the “InsertOnSubmit” function of ORM. The following code is working fine. However, if I the company is introducing a new payment component using credit card, I need to make changes to my “Payment” domain class. How do I make the payment class Open for Extension and Closed for Changes still using ORM?

Note: The Payment class has behaviors (E.g. GetTotalAmountCollected). I am trying to make the "Payment" class to satisfy OCP.

Note: There is a specific behavior for Coupon type. Is the Coupon issued date is less than 1/1/2000, it should not be used in calculation for Total Amount (i.e, CouponValue should be zero). Refer Refactoring code using Strategy Pattern also.

Note: I am using .Net 4.0

Reference:

  1. Getting an error when using ObjectContext.AddObject with Entity Framework
  2. Refactoring code using Strategy Pattern
  3. Prefer composition over inheritance?
  4. Code-first vs Model/Database-first
  5. Strategy Pattern and Dependency Injection using Unity
  6. C# Strategy Design Pattern by Delegate vs OOP
  7. How to use the Strategy Pattern with C#?
  8. Inheritance with EF Code First: Part 2 – Table per Type (TPT) http://weblogs.asp.net/manavi/archive/2010/12/28/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt.aspx

C# Code:

public class PaymentAppService
{
    public RepositoryLayer.ILijosPaymentRepository Repository { get; set; }

    public void MakePayment()
    {
        DBML_Project.Payment paymentEntity = new DBML_Project.Payment();
        paymentEntity.PaymentID = 1;
        paymentEntity.PaymentType = "PurchaseP";

        DBML_Project.CashPayment cashObj = new DBML_Project.CashPayment();
        cashObj.CashPaymentID = 1;
        cashObj.CurrencyNumber = 123;
        cashObj.CurrencyValue = 100;

        DBML_Project.GiftCouponPayment giftCouponObj = new DBML_Project.GiftCouponPayment();
        giftCouponObj.GiftCouponPaymentID = 1;
        giftCouponObj.CouponValue = 200;
        giftCouponObj.CouponNumber = 124;

        paymentEntity.CashPayments = new System.Data.Linq.EntitySet<DBML_Project.CashPayment>();
        paymentEntity.CashPayments.Add(cashObj);

        paymentEntity.GiftCouponPayments = new System.Data.Linq.EntitySet<DBML_Project.GiftCouponPayment>();
        paymentEntity.GiftCouponPayments.Add(giftCouponObj);

        Repository.InsertEntity(paymentEntity);
        Repository.SubmitChanges();
    }
}

Repository:

public class LijosPaymentRepository : ILijosPaymentRepository
{
    public System.Data.Linq.DataContext MyDataContext { get; set; }

    public void InsertEntity(DBML_Project.Payment payment)
    {
        //Insert the entity
        MyDataContext.GetTable<DBML_Project.Payment>().InsertOnSubmit(payment);
    }

    public void SubmitChanges()
    {
        MyDataContext.SubmitChanges();
    }
}
Community
  • 1
  • 1
LCJ
  • 22,196
  • 67
  • 260
  • 418
  • So LINQ to SQL, or Entity Framework and thus LINQ to Entities? – abatishchev Jul 11 '12 at 07:25
  • @Lijo - What do you mean by : Open for Extension and Closed for Changes still using ORM? – bhuvin Jul 11 '12 at 11:29
  • @bhuvin It is a statement from Open-Closed Principle. I cannot get rid of ORM to satisfy this principle. – LCJ Jul 11 '12 at 11:36
  • Your example payment classes are only dumb data objects without behavior. The Open/Closed Principle is not about managing properties, I believe, but managing class behavior in a more maintainable and extensible way. I'd suggest you're overthinking this - if you want to treat data classes in a polymorphic way, use abstraction. – Remi Despres-Smyth Jul 11 '12 at 12:23
  • @RemiDespres-Smyth The Payment class has behaviors (E.g. GetTotalAmountCollected). I am trying to make the "Payment" class to satisfy OCP. – LCJ Jul 11 '12 at 12:27
  • 1
    @Lijo Is there behavior of different types of payments you are already aware you want or need to extend in different ways? – Remi Despres-Smyth Jul 11 '12 at 12:30
  • 1
    @Lijo Don't start with "I want to satisfy the OCP". Start with "this is what I need to be able to do with payment classes". From there, figure out how to design something to meet those objectives - and follow SOLID, sure. Given the scenario you've outlined at the moment, I see no reason for a more complicated design than simply using abstraction - GetTotalAmountCollected is an abstract method on the base class, and each derived class defines it for itself. Your Coupon type class returns $0 if the date is less than 1/1/2000. – Remi Despres-Smyth Jul 11 '12 at 12:40
  • 1
    @Lijo If you really wanted to be able to extend, you could always define an ICalculateCouponValueStrategy interface that gets injected into your CouponPayment class or something (so you can vary how the coupon calculation occurs), but I personally think it's overkill unless you're confident you'll need it in the future. Instead, I'd start with the simpler scenario and refactor if required in the future. – Remi Despres-Smyth Jul 11 '12 at 12:45
  • @RemiDespres-Smyth It would be great if you can post an answer that shows how "ICalculateCouponValueStrategy interface that gets injected into your CouponPayment class" – LCJ Jul 11 '12 at 12:51
  • 1
    Sorry, I won't do that this morning. The injection is easy though - just constructor parameter. And you can delegate your call to GetTotalAmountCollected to this instance. The bit I don't want to get into is how you determine which concrete implementation of ICalculateCouponValueStrategy gets injected, or how/where you do so when using Linq-to-SQL, which I'm not overly familiar with. – Remi Despres-Smyth Jul 11 '12 at 12:56

4 Answers4

5

For the problem that @Lijo tries to solve the abstract approach would be better

I think you can make a partial class on the CashPayment type that implements your own IPayment interface, which can be used through the whole application. This interface can then also be on CreditCardPayment:

Example:



public interface IPayment
{
     int Id { get; set; }
     int PaymentId { get; set; }
     //Other payment specific properties or methods
}

public partial class CashPayment : IPayment
{
    public int Id 
    {
       get { return CashPaymentId ; }
       set { CashPaymentId = value; }
    }

    //Other properties

}

public partial class CreditCardPayment : IPayment 
{
   //more code ...
}

Something on your EF context to get all payments




public partial class PaymentEntities //The name of your EF entities
{
   public IQueryable AllPayments
   {
      return this.CashPayment.Union(this.CreditCardPayment); //This is not good, but just an example. The abstract class approach would be better here.
   }

    public void InsertPayment(IPayment payment)
    {
         this.AddObject(payment.GetType().Name, payment);
    }
}

Preben Huybrechts
  • 5,853
  • 2
  • 27
  • 63
  • You can do something similair with IQueryables on your Entities of EF. But then you could also use the Abstract class approach, to make things a little easier. Disadvantage of this approach is your Database can and probably will change more than you like. – Preben Huybrechts Jul 11 '12 at 06:47
  • 1
    Look into this: http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/implementing-inheritance-with-the-entity-framework-in-an-asp-net-mvc-application – Preben Huybrechts Jul 11 '12 at 10:57
  • I am wondering how the AllPayments method can be implemented as Abstract. Can you please explain? public IEnumerable AllPayments(){//return this.} – LCJ Jul 13 '12 at 11:28
  • 1
    If you use Abstracts and EF you can just query your Abstract Type on your EF context: I.E.: context.YourAbstractClass; will return all objects in the database that inherit from your abstract class. – Preben Huybrechts Jul 13 '12 at 13:10
2

Maybe one alternative would be to take advantage of Inheritance, in the ORM as well. So that instead of having N collections, one for each type in the Payment Entity. You would have all of the subtypes in the same collection. And all those, added up would represent the entire Payment.

Maybe it would be easier to name things a bit differently. For example, let's consider the concept of a Purchase. Purchase should have a collection of Payments. And Payment could be an abstract class, from which Cash, Coupon, CreditCard, all inherit.

Having the model set up that way opens a great deal of possibilities regarding how you can solve certain problems. You can treat all payments the same and forget about distinct collections, plus have a great deal of control through polymorphism and double dispatch.

That way, if a new payment type arises, your model will remain the same, you will just have a new subtype.

Most ORMs nowadays support different persistence schemes for inheritance, that will help keep your data structure clean as well.

Pablo Romeo
  • 11,298
  • 2
  • 30
  • 58
  • Do you mean I need to use Inheritance rather than composition/association? "And Payment could be an abstract class". Can you provide the database design change required also? – LCJ Jul 11 '12 at 06:54
  • 2
    Well, it would still be composition. Since a Purchase would be made up of a series of Payments. It's just that Purchase would have a collection of Payments, and within that collection you'd find a mix of payments of different types. In this scenario, Payment would be abstract. The concrete options would be Cash, Coupon, CreditCard, etc. It's not that you NEED to use it, it's just a different way of solving the same problem. With this solution, doing things like calculating totals would be much easier, since it would just be adding up all totals from the payments. – Pablo Romeo Jul 11 '12 at 07:05
  • Thanks. Can you please show how the database design would be based on this updated model design? – LCJ Jul 11 '12 at 07:16
2

What is the added value of creating a new type for each type of payment ? I can see the distinction between a 'Payment by money', and a 'Payment done via a voucher'. I can agree to have two different types to make that distinction.

However, why make the distinction between a CashPayment, a CreditCardPayment, etc ... ? Do you have to store extra information depending on the type of payment ? Does the behaviour change ?

Why don't you just keep it simple, and add an extra property to the regular 'Payment' type, which acts as a discriminator and gives you the information how the payment was made (via creditcard, cash, .... ) ?

public class Payment {}

public class VoucherPayment : Payment {}

public class MoneyPayment : Payment
{
    public PaymentMode { get; set; }
}

public enum PaymentMode 
{
    Cash,
    CreditCard
}
Frederik Gheysels
  • 56,135
  • 11
  • 101
  • 154
  • There is a specific behavior for Coupon type. Is the Coupon issued date is less than 1/1/2000, it should not be used in calculation for Total Amount (i.e, CouponValue should be zero). What can we do in this scenario? – LCJ Jul 11 '12 at 12:45
  • As I said in my post, for coupon or voucher payments, an additional type is justified. That type could have a method 'GetValue' which returns the value of the coupon, and returns zero if the issue-date is before 1/1/2000 – Frederik Gheysels Jul 11 '12 at 12:58
1

You could define your model using table-per-hierarchy (http://msdn.microsoft.com/en-us/library/bb738443) which uses a discriminator (i.e., PaymentType in your case) column to specify the particular flavor of the payment you are making. Then you would be beholden to adding columns if new payment types required more data; however, EF is fairly flexible in terms of how you define entities and how those entities map to your data store. You probably could have a common set of columns like Number and Value and then go all sharepointy and add columns with generic names like StringValue1 to the database. You could then map those columns to better named properties in your conceptual model that would make more sense to your domain.

This basically flattens your database schema into a single table, but your model remains separate classes. You can subclass your Payment class for additional payment types without affecting either the data store or the Payment class; however, you would need to modify the model to add new payment types and map them to the appropriate columns.

Jason
  • 4,897
  • 2
  • 33
  • 40