Extending The Kinetic Framework

Ok. So you've been using the Kinetic framework (or you haven't, but are thinking about it). One thing keeps nagging you, though.

What if I want to do something that's not in the framework by default?

Good question! The framework is built to be very flexible and allow you to do just that! If you're talking about extending Entities, there's two major ways:

1. Add functionality to an existing entity
2. Make a new entity based on an existing entity

Sticking with the Northwind database as a reference, let's say you want to change a few things when working with a Customer object. First, we'll look at adding functionality to an existing entity. A customer has orders, and an order has order lines, and order lines have products they are associated with. But a customer has no direct association with a product. What if we want to see a list of all products that a customer has purchased? Ideally, if we have a Customer entiity called customer, it would be nice to have that stored in a customer.Products property. But it's not there.

This is where adding functionality to an existing class comes in handy. We can add any functionality we want in the Customer.cs class file. We add the Product collection, and, following the pattern of lazy loading, we get this:

private EntityListReadOnly<Product> _products = null;
public EntityListReadOnly<Product> Products
{
   get
   {
      If(_products == null)
     {
        _products = Product.GetProductsByCustomer(this);
     }
     return _products;
   }
}


We don't have a setter because we can't directly change this collection - it's just a roll up of data available other ways in the system.

All set then, right? Well, not quite. We did the easy part. We have a collection, and we lazy load it. But where do we lazy load it from? The Product entity, but right now, it doesn't have a method for that. So, we need to add one:
public static EntityList<Product> GetProductsByCustomer(Customer customer)
{
  string commandText = "ProductGetByCustomer";
  List<SqlParameter> parameters = new List<SqlParameter>();
  parameters.add(new SqlParameter("@CustomerId", customer.CustomerId));
  return GetList<Product>(customer, commandText, parameters);
}

Obviously you need to create the stored procedure for getting products by customer, or if you decide to use dynamic SQL, then you can just write the SQL right here. In that version, it'd look something like this:
public static EntityList<Product> GetProductsByCustomer(Customer customer)
{
  string commandText = @"
  SELECT DISTINCTProducts.*
  FROM Products
  INNER JOIN OrderDetails 
  ON OrderDetails.ProductId = Products.ProductId
  INNER JOIN Orders 
  ON Orders.OrderId = OrderDetails.OrderId
  WHERE Orders.CustomerId = @CustomerId;";

  List<SqlParameter> parameters = new List<SqlParameter>();
  parameters.add(new SqlParameter("@CustomerId", customer.CustomerId));
  return GetList<Product>(commandText, parameters);
}

Now, when you call customer.Products, you'll get a collection of all products that the customer has ordered. That's it!

The second way to extent the framework is to inherit from an object. So let's do that. Let's create a summary customer object that shows how many orders each customer has. We could get that info another way - customer.Orders.Count, but that would require loading every order just to get the count - not efficient at all, especially if you want to display a list of customers.

So what can we do? We want the customer info, plus some more data, so we start with the customer entity as our base, and add a property for the number of orders
public class CustomerSummary : Customer
{
  protected CustomerSummary() { }

  private int _numberOfOrders;
  {"[DatabaseColumn()]"}
  public int NumberOfOrders
  {
    get { return _numberOfOrders; }
    protected set { _numberOfOrders = value; }
  }
}

Let's back up and explain a few things. We have a protected CustomerSummary constructor for two reasons: First, it follows the framework's pattern of using Factory methods to get and/or create entities. Second, a CustomerSummary object isn't what you'd be using to create a new customer - it's meant for displaying data. The DatabaseColumn() attribute is added to the NumberOfOrders property to tell the framework to expect this value to be retrieved when we run stored procedures or queries to get CustomerSummary objects. Now, like I said before, the CustomerSummary is read-only, so why do we have a setter for NumberOfOrders? Because that's the setter that the framework will use to populate the number of orders. We make it protected so only the framework can get access to it.

Next we need a method to get CustomerSummary objects:
public EntityList<CustomerSummary> GetCustomerSummaries()
{
  string commandText = "CustomerSummaryGet";
  list<SqlParameter> parameters = new List<SqlParameter>();
  return GetList<CustomerSummary>(commandText, parameters);
}

To better reflect what's happening, let's take a look at the Dynamic SQL version:
public EntityList<CustomerSummary> GetCustomerSummaries()
{
  string commandText = @"
  SELECT 
  Customers.*,
  COUNT(Orders.OrderID) AS 'NumberOfOrders'
  FROM Customers
  INNER JOIN Orders
  ON Customers.CustomerID = [Orders.[CustomerID]
  GROUP BY
  Customers.*
  ";

  list<SqlParameter> parameters = new List<SqlParameter>();
  return GetList<CustomerSummary>(commandText, parameters);
}

First, no, I'm not positive that the group by clause works - basically, it's a listing of all of the fields in the Customer table (starting with CustomerID, since it's unique) because having a count() in your query requires that you group by all of the other fields in the query.

Anyway, does that look familiar? It should. It's very similar to what we had to add to the Product class to get a list of products by customer - the query is different, but the rest of the calls are the same. The call to GetList() is slightly different because it specifies we're getting a list of CustomerSummary entities instead of Product entities, but that's it. The pattern for getting a list of objects is the same whether you are working in a framework generated object or you inherit from one.

Last edited Jul 17, 2008 at 1:58 AM by RossCode, version 7

Comments

No comments yet.