Click here to Skip to main content
Click here to Skip to main content

Extending Objects

, 28 May 2014
Rate this:
Please Sign up or sign in to vote.
Discuss how to change software by extending objects

Introduction

Traditionally, you extend software by adding or modifying classes, and then, instantiate and use objects of classes. We call this class design. So, class design is an intermediate step in extending software. Would it be nice to extend object directly without adding or modifying classes? In this article, I discuss adding functions to object using extension methods WithBehaviors<T>() and ActLikeWithBehaviors<T>().

Object Extension Methods

The following two extension methods can be used to add functions to object.

public static T WithBehaviors<T>(this object target, String[] arrMethods, Decoration preAspect, Decoration postAspect)
public static T ActLikeWithBehaviors<T>(this object target, String[] arrMethods, Decoration preAspect, Decoration postAspect)

WithBehaviors<T>() is used to add functions to object whose class implements an interface T while ActLikeWithBehaviors<T>() is used to add functions to object whose class does not implement an interface T. In the following sections, I discuss how they are used to extend object.

Using the Code

Let's start with a simple application that inserts a record into the [Sales].[SalesOrderHeader] and [Sales].[SalesOrderDetail] tables of the AdventureWorks database shipped with Microsoft SQL Server. Then, WithBehaviors<T>() and ActLikeWithBehaviors<T>() are used to extend it to ensure the two insertions are managed by a transaction. This is a re-writing of the same example used in Object Decoration With Impromptu-Interface.

First, let's define two classes Order and OrderDetail. Order has one method to insert an order to [SalesOrderHeader] table while OrderDetail has one method to insert the order details to [SalesOrderDetail] table.

public class Order 
{
    public SqlCommand Comm { get; set; }

    public Order() { }

    public Order(SqlCommand comm, SqlConnection conn)
    {
        Comm = comm;
        Comm.Connection = conn;
    }

    public int InsertOrder(int CustomerID, DateTime DueDate, string AccountNumber, int ContactID, int BillToAddressID, int ShipToAddressID, int ShipMethodID, double SubTotal, double TaxAmt)
    {
        string sqlStr = @"INSERT [Sales].[SalesOrderHeader] 
([CustomerID], [DueDate], [AccountNumber], [ContactID], [BillToAddressID], 
[ShipToAddressID], [ShipMethodID], [SubTotal], [TaxAmt]) values
(@CustomerID, @DueDate, @AccountNumber, @ContactID, @BillToAddressID,
@ShipToAddressID, @ShipMethodID, @SubTotal, @TaxAmt); SET @scopeId = SCOPE_IDENTITY()";

        Comm.CommandText = sqlStr;
        Comm.CommandType = CommandType.Text;

        SqlParameter CustomerIDParameter = new SqlParameter("@CustomerID", SqlDbType.Int);
        CustomerIDParameter.Direction = ParameterDirection.Input;
        CustomerIDParameter.Value = CustomerID;
        Comm.Parameters.Add(CustomerIDParameter);

        SqlParameter DueDateParameter = new SqlParameter("@DueDate", SqlDbType.DateTime);
        DueDateParameter.Direction = ParameterDirection.Input;
        DueDateParameter.Value = DueDate;
        Comm.Parameters.Add(DueDateParameter);

        SqlParameter AccountNumberParameter = new SqlParameter("@AccountNumber", SqlDbType.Text);
        AccountNumberParameter.Direction = ParameterDirection.Input;
        AccountNumberParameter.Value = AccountNumber;
        Comm.Parameters.Add(AccountNumberParameter);

        SqlParameter ContactIDParameter = new SqlParameter("@ContactID", SqlDbType.Int);
        ContactIDParameter.Direction = ParameterDirection.Input;
        ContactIDParameter.Value = ContactID;
        Comm.Parameters.Add(ContactIDParameter);

        SqlParameter BillToAddressIDParameter = new SqlParameter("@BillToAddressID", SqlDbType.Int);
        BillToAddressIDParameter.Direction = ParameterDirection.Input;
        BillToAddressIDParameter.Value = BillToAddressID;
        Comm.Parameters.Add(BillToAddressIDParameter);

        SqlParameter ShipToAddressIDParameter = new SqlParameter("@ShipToAddressID", SqlDbType.Int);
        ShipToAddressIDParameter.Direction = ParameterDirection.Input;
        ShipToAddressIDParameter.Value = ShipToAddressID;
        Comm.Parameters.Add(ShipToAddressIDParameter);

        SqlParameter ShipMethodIDParameter = new SqlParameter("@ShipMethodID", SqlDbType.Int);
        ShipMethodIDParameter.Direction = ParameterDirection.Input;
        ShipMethodIDParameter.Value = ShipMethodID;
        Comm.Parameters.Add(ShipMethodIDParameter);

        SqlParameter SubTotalParameter = new SqlParameter("@SubTotal", SqlDbType.Float);
        SubTotalParameter.Direction = ParameterDirection.Input;
        SubTotalParameter.Value = SubTotal;
        Comm.Parameters.Add(SubTotalParameter);

        SqlParameter TaxAmtParameter = new SqlParameter("@TaxAmt", SqlDbType.Int);
        TaxAmtParameter.Direction = ParameterDirection.Input;
        TaxAmtParameter.Value = TaxAmt;
        Comm.Parameters.Add(TaxAmtParameter);

        SqlParameter scopeIDParameter = new SqlParameter("@scopeId", SqlDbType.Int);
        scopeIDParameter.Direction = ParameterDirection.Output;
        Comm.Parameters.Add(scopeIDParameter);

        Comm.ExecuteNonQuery();

        return (int)scopeIDParameter.Value;
    }
}

public class OrderDetail
{
    public SqlCommand Comm { get; set; }

    public OrderDetail() { }

    public OrderDetail(SqlCommand comm, SqlConnection conn)
    {
        Comm = comm;
        Comm.Connection = conn;
    }

    public int InsertOrderDetail(int SalesOrderID, int OrderQty, int ProductID, int SpecialOfferID, double UnitPrice)
    {
        string sqlStr = @"INSERT INTO [Sales].[SalesOrderDetail] 
([SalesOrderID], [OrderQty], [ProductID], [SpecialOfferID], [UnitPrice]) values
(@orderID, @OrderQty, @ProductID, @SpecialOfferID, @UnitPrice)";

        Comm.CommandText = sqlStr;
        Comm.CommandType = CommandType.Text;

        SqlParameter orderIDParameter = new SqlParameter("@orderID", SqlDbType.Int);
        orderIDParameter.Direction = ParameterDirection.Input;
        orderIDParameter.Value = SalesOrderID;
        Comm.Parameters.Add(orderIDParameter);

        SqlParameter OrderQtyParameter = new SqlParameter("@OrderQty", SqlDbType.Int);
        OrderQtyParameter.Direction = ParameterDirection.Input;
        OrderQtyParameter.Value = OrderQty;
        Comm.Parameters.Add(OrderQtyParameter);

        SqlParameter ProductIDParameter = new SqlParameter("@ProductID", SqlDbType.Int);
        ProductIDParameter.Direction = ParameterDirection.Input;
        ProductIDParameter.Value = ProductID;
        Comm.Parameters.Add(ProductIDParameter);

        SqlParameter SpecialOfferIDParameter = new SqlParameter("@SpecialOfferID", SqlDbType.Int);
        SpecialOfferIDParameter.Direction = ParameterDirection.Input;
        SpecialOfferIDParameter.Value = SpecialOfferID;
        Comm.Parameters.Add(SpecialOfferIDParameter);

        SqlParameter UnitPriceParameter = new SqlParameter("@UnitPrice", SqlDbType.Float);
        UnitPriceParameter.Direction = ParameterDirection.Input;
        UnitPriceParameter.Value = UnitPrice;
        Comm.Parameters.Add(UnitPriceParameter);

        return Comm.ExecuteNonQuery();
    }
}

The following code does the actually insertions.

class Program
{
    static void Main(string[] args)
    {
        string connStr = "Integrated Security=true;Data Source=(local);Initial Catalog=AdventureWorks";
        using (SqlConnection conn = new SqlConnection(connStr))
        {
            conn.Open();

            try
            {
                int iOrderId = new Order(new SqlCommand(), conn).InsertOrder(18759, DateTime.Now.AddDays(1), "10-4030-018759", 4189, 14024, 14024, 1, 174.20, 10);

                //throw new Exception();

                int iStatus = new OrderDetail(new SqlCommand(), conn).InsertOrderDetail(iOrderId, 5, 708, 1, 28.84);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            conn.Close();
        }
    }
}

Run the above code, you will see one record is inserted into [SalesOrderHeader] and one record is inserted into [SalesOrderDetail].

Transaction Management

There is a problem with the above code. What about an exception is thrown after execution of the InsertOrder but prior to completion of the InsertOrderDetail? You end up with a dangling order without details! So, you need to put the two insertions in one transaction to ensure your data integrity.

Since Order and OrderDetail do not implement interfaces, we need to define interfaces to wrap them in. The two interfaces are defined as follows.

public interface IOrder
{
    SqlCommand Comm { get; set; }
    int InsertOrder(int CustomerID, DateTime DueDate, string AccountNumber, int ContactID, int BillToAddressID, int ShipToAddressID, int ShipMethodID, double SubTotal, double TaxAmt);
}

public interface IOrderDetail
{
    SqlCommand Comm { get; set; }
    int InsertOrderDetail(int SalesOrderID, int OrderQty, int ProductID, int SpecialOfferID, double UnitPrice);
}

Next, we need to have a function to manage transaction which is defined as follows.

public static void JoinSqlTransaction(AspectContext ctx, dynamic parameter)
{
    try
    {
        ctx.Target.Comm.Transaction = parameter;
        return;
    }
    catch (Exception ex)
    {
    }
}

Now, we are ready to use ActLikeWithBehaviors<T>() to wrap an object in interface and put it in a transaction. The complete code is as follows.

static void Main(string[] args)
{
    string connStr = "Integrated Security=true;Data Source=(local);Initial Catalog=AdventureWorks";
    using (SqlConnection conn = new SqlConnection(connStr))
    {
        conn.Open();
        SqlTransaction transaction = conn.BeginTransaction();

        try
        {
            int iOrderId = new Order(new SqlCommand(), conn).ActLikeWithBehaviors<IOrder>(new string[] { "InsertOrder" },
                new Decoration(JoinSqlTransaction, transaction),
                null
            ).InsertOrder(18759, DateTime.Now.AddDays(1), "10-4030-018759", 4189, 14024, 14024, 1, 174.20, 10);

            //throw new Exception();

            int iStatus = new OrderDetail(new SqlCommand(), conn).ActLikeWithBehaviors<IOrderDetail>(new string[] { "InsertOrderDetail" },
                new Decoration(JoinSqlTransaction, transaction),
                null
            ).InsertOrderDetail(iOrderId, 5, 708, 1, 28.84);

            transaction.Commit();
        }
        catch (Exception ex)
        {
            if (transaction != null)
                transaction.Rollback();
        }

        conn.Close();
    }
}

In the above code, an object of Order is instantiated, which calls extension method ActLikeWithBehaviors<IOrder>(). This extension method does two things. First, it wraps the object in interface IOrder. Second, it attaches the transaction function JoinSqlTransaction to the method InsertOrder. Similarly, an object of OrderDetail is created and the extension method is used to wrap the object in interface IOrderDetail and attach JoinSqlTransaction to the method InsertOrderDetail.

Run the above code, you will see one record is inserted to table [SalesOrderHeader] and one record is inserted to table [SalesOrderDetail].

Uncomment the code //throw new Exception(); and run it, no record is added to the tables. The insertion operations are managed by transaction and the data integrity is ensured.

If the Order and OrderDetail have implemented the interfaces IOrder and IOrderDetail, respectively, there is no need to wrap them in the interfaces. Instead, you should replace ActLikeWithBehaviors<T>() with WithBehaviors<T>() in the above code. WithBehaviors<T>() extension method adds functions to methods of object. Please refer to the code download for details.

Points of Interest

The object extension methods add functions to object without class design.

The above example shows how transaction management is added to objects. To see how logging, security checking and sorting are added to objects, click here (Note: The code example in this link is old but you should be able to rewrite it easily using the extesion methods discussed in this article.).

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Gary H Guo

United States United States
Object-oriented (OO) is about "classes" not "objects". But I truly believe that "objects" deserve more our attentions. If you agree, read more on... Dynamic Object Programming (DOP), Component-Based Object Extender (CBO Extender), AOP Container and Dynamic Decorator Pattern.
 
Mobile development is not just another type of front end. The real challenge is actually in the back end: How to present meaningful information in time to mobile users with exponentially increased data flooding around? Here is my first mobile solution: SmartBars - Barcode Reader, Price Comparison and Coupons.
 
Gary lives in southeast Michigan. My first programming language is FORTRAN. For the last a few years, I have primarily focused on .NET technologies with Mobile Development as my newest interest.

Comments and Discussions

 
GeneralMy vote of 5 PinpremiumVolynsky Alex23-May-14 12:05 
GeneralRe: My vote of 5 PinpremiumGary H Guo25-May-14 16:06 
QuestionRe: My vote of 5 PinpremiumVolynsky Alex25-May-14 20:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140827.1 | Last Updated 28 May 2014
Article Copyright 2014 by Gary H Guo
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid