My Turn - Critique My BLL Design

Posts   
 
    
bertcord avatar
bertcord
User
Posts: 206
Joined: 01-Dec-2003
# Posted on: 08-Jul-2004 19:10:01   

I have been messing around with some ideas and woudl love to get some of your guys input. I plan on giving you guys the codesmith templates I use to create these classes when I am complete. My idea is to basically create a Controller class for each entity in my project. The controller class would have the following methods. Using the Northwind sample project and the Order Entity as an example.

FetchOrderEntity - Would return a single entity based on the PK value passed in.

OrderEntity myorder = OrderController.FetchOrderEntity(10248); 

FetchOrderCollection - A couple overloads of this method check it out below. A specific overload that I am finding helpful is a method that would except a parameter for each FK related entity. For orders this would be CustomerEntity,EmployeeEntity, ShipperEntity. Having this would allow me to do this


CustomerEntity customer = new CustomerEntity("ALFKI");
ShipperEntity shipper = new ShipperEntity(1);
EntityCollection orders2 = OrderController.FetchOrderCollection(customer2, null,shipper);

I also create methods for each relation for example if you have an order object and want to fetch all related OrderDetails (1:n) and its related Shipper (m:1) you could call the following.


OrderEntity myorder = OrderController.FetchOrderEntity(10248);
OrderController.FetchShipperEntity(myorder);
OrderController.FetchOrderDetailsCollection(myorder);

The myorder object is now populated

 Console.WriteLine(myorder.Shipper.CompanyName);
            Console.WriteLine(myorder.OrderDetails.Count);

Here a few questions I have...

  1. At first I thought about subclassesing the Dataaccess adapter object...but I when I started thinking about the save logic I decided against it. I want the ability to support transactions so I am going to create overloads for the saves that has an adapter object as a parameter.

  2. Many of the apps I am working on are simple reporting types aps...the majority of the time I need a default sort. In my controller class I create a DefaultOrderSorter object that is used in many of my FetchOrderCollection methods. IS this the correct way to do this?

static public SortExpression DefaultOrderSorter = new SortExpression(SortClauseFactory.Create(OrderFieldIndex.OrderID, SortOperator.Ascending));

I have pasted below the OrderController object and if you want to download the entire project you can here. http://www.corderman.com/Portals/0/NorthwindBL.zip

Entire OrderController



using Northwind.HelperClasses;
using Northwind.DatabaseSpecific;
using SD.LLBLGen.Pro.ORMSupportClasses;
using Northwind.FactoryClasses;
using Northwind.EntityClasses;
using System.Configuration;

namespace Northwind.BL
{
    /// <summary>
    /// Summary description for OrderController.
    /// </summary>
    public class OrderController
    {
        public static string ConnectionString 
        {
            get
            {
                return ConfigurationSettings.AppSettings["Main.ConnectionString"];
            }
        }

        static public SortExpression DefaultOrderSorter = new SortExpression(SortClauseFactory.Create(OrderFieldIndex.OrderID, SortOperator.Ascending));
        
        /// <summary>
        /// Fetches a collection of all Order entities sorted by the default sorter.
        /// </summary>
        /// <returns>EntityCollection</returns>
        public static EntityCollection FetchOrderCollection()
        {
            return FetchOrderCollection(null,0,DefaultOrderSorter);
        }
        
        /// <summary>
        /// Fetches one or more Order entities which match the filter information in the filterBucket.
        /// </summary>
        /// <param name="filterBucket">filter information for retrieving the Order entities. If null, all Order entities are returned.</param>
        /// <param name="maxNumberOfItemsToReturn">The maximum amount of Order entities to return. If 0, all Order entities matching the filter are returned</param>
        /// <returns></returns>
        public static EntityCollection FetchOrderCollection(IRelationPredicateBucket filterBucket,int maxNumberOfItemsToReturn)
        {
            return FetchOrderCollection(filterBucket,0,null);
        }
        
        /// <summary>
        /// Fetches one or more Order entities which match the filter information in the filterBucket.
        /// </summary>
        /// <param name="filterBucket">filter information for retrieving the Order entities. If null, all Order entities are returned.</param>
        /// <param name="maxNumberOfItemsToReturn">The maximum amount of Order entities to return. If 0, all Order entities matching the filter are returned</param>
        /// <param name="sortClauses"></param>
        /// <returns></returns>
        public static EntityCollection FetchOrderCollection(IRelationPredicateBucket filterBucket,int maxNumberOfItemsToReturn, ISortExpression sortClauses)        
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            EntityCollection OrderCollection = new EntityCollection(new Northwind.FactoryClasses.OrderEntityFactory());
            adapter.FetchEntityCollection(OrderCollection,filterBucket,0,sortClauses);
            return OrderCollection;
        }

        /// <summary>
        /// Fetches a collection of all Order entities filtered by its foreign keys
        /// </summary>
        /// <param name="customer">Customer entity to filter by </param>
        /// <param name="employee">Employee entity to filter by </param>
        /// <param name="shipper">Shipper entity to filter by </param>
        /// <returns>EntityCollection</returns>
        public static EntityCollection FetchOrderCollection(CustomerEntity customer, EmployeeEntity employee, ShipperEntity shipper)
        {
            RelationPredicateBucket bucket = new RelationPredicateBucket();
                if (customer != null)
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.CustomerID, ComparisonOperator.Equal, customer.CustomerID));
                }
                if (employee != null)
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.EmployeeID, ComparisonOperator.Equal, employee.EmployeeID));
                }
                if (shipper != null)
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.ShipVia, ComparisonOperator.Equal, shipper.ShipperID));
                }
            
            return FetchOrderCollection(bucket,0,DefaultOrderSorter);
        }
        
        /// <summary>
        /// Fetches a collection of all Order entities filtered by its foreign keys
        /// </summary>
        /// <param name="customer">Customer entity to filter by </param>
        /// <param name="employee">Employee entity to filter by </param>
        /// <param name="shipper">Shipper entity to filter by </param>
        /// <returns>EntityCollection</returns>
        public static EntityCollection FetchOrderCollection(System.String CustomerID, System.Int32 EmployeeID, System.Int32 ShipVia)
        {
            RelationPredicateBucket bucket = new RelationPredicateBucket();
                if (!Null.IsNull(CustomerID))
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.CustomerID, ComparisonOperator.Equal, CustomerID));
                }
                if (!Null.IsNull(EmployeeID))
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.EmployeeID, ComparisonOperator.Equal, EmployeeID));
                }
                if (!Null.IsNull(ShipVia))
                {
                    bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderFieldIndex.ShipVia, ComparisonOperator.Equal, ShipVia));
                }
            
            return FetchOrderCollection(bucket,0,DefaultOrderSorter);
        }
        
        /// <summary>
        /// Fetches an Order entity from the persistent storage using a primary key filter. 
        /// </summary>
        /// <returns>Order entity</returns>
        public static OrderEntity FetchOrderEntity(System.Int32 OrderID)
        {
            DataAccessAdapter adapter = new DataAccessAdapter();
            OrderEntity order = new OrderEntity(OrderID);
            adapter.FetchEntity(order);
            return order;
        }
        
        
        /// <summary>
        /// OneToMany
        /// Returns a typed collection with one or more instances of the entity 'OrderDetails' which are directly related to the instance of the entity 'Order' where Order.OrderID=OrderDetails.OrderID
        /// </summary>
        /// <returns></returns>
        public static EntityCollection FetchOrderDetailsCollection(OrderEntity order)
        {
            
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            EntityCollection OrderDetailsCollection = order.OrderDetails;
            SortExpression OrderDetailsSorter = new SortExpression(SortClauseFactory.Create(OrderDetailsFieldIndex.OrderID, SortOperator.Ascending));
                
            adapter.FetchEntityCollection(OrderDetailsCollection,order.GetRelationInfoOrderDetails(),0,OrderDetailsSorter);
            return OrderDetailsCollection;
        }
    
        /// <summary>
        /// OneToMany
        /// Returns a typed collection with one or more instances of the entity 'OrderDetails' which are directly related to the instance of the entity 'Order' where Order.OrderID=OrderDetails.OrderID
        /// </summary>
        /// <returns></returns>
        public static EntityCollection FetchOrderDetailsCollection(System.Int32 OrderID)
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            EntityCollection OrderDetailsCollection = new EntityCollection(new OrderDetailsEntityFactory());
            SortExpression OrderDetailsSorter = new SortExpression(SortClauseFactory.Create(OrderDetailsFieldIndex.OrderID, SortOperator.Ascending));
            
            RelationPredicateBucket bucket = new RelationPredicateBucket();
            bucket.PredicateExpression.Add(PredicateFactory.CompareValue(OrderDetailsFieldIndex.OrderID, ComparisonOperator.Equal, OrderID));
            adapter.FetchEntityCollection(OrderDetailsCollection,bucket,0,OrderDetailsSorter);

            return OrderDetailsCollection;
        }

    
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Customer' which are directly related to the instance of the entity 'Order' where Order.CustomerID=Customer.CustomerID
        /// </summary>
        /// <returns></returns>
        public static CustomerEntity FetchCustomerEntity(OrderEntity Order)
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            Order.Customer = (CustomerEntity) adapter.FetchNewEntity(new CustomerEntityFactory(), Order.GetRelationInfoCustomer());
            return Order.Customer;
        }
        
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Customer' which are directly related to the instance of the entity 'Order' where Order.CustomerID=Customer.CustomerID
        /// </summary>
        /// <returns></returns>
        public static CustomerEntity FetchCustomerEnity(System.String CustomerID)
        {
                DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
                CustomerEntity Customer = new CustomerEntity(CustomerID);
                
                adapter.FetchEntity(Customer);
    
                return Customer;
        }
    
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Employee' which are directly related to the instance of the entity 'Order' where Order.EmployeeID=Employee.EmployeeID
        /// </summary>
        /// <returns></returns>
        public static EmployeeEntity FetchEmployeeEntity(OrderEntity Order)
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            Order.Employee = (EmployeeEntity) adapter.FetchNewEntity(new EmployeeEntityFactory(), Order.GetRelationInfoEmployee());
            return Order.Employee;
        }
        
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Employee' which are directly related to the instance of the entity 'Order' where Order.EmployeeID=Employee.EmployeeID
        /// </summary>
        /// <returns></returns>
        public static EmployeeEntity FetchEmployeeEnity(System.Int32 EmployeeID)
        {
                DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
                EmployeeEntity Employee = new EmployeeEntity(EmployeeID);
                
                adapter.FetchEntity(Employee);
    
                return Employee;
        }
    
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Shipper' which are directly related to the instance of the entity 'Order' where Order.ShipVia=Shipper.ShipperID
        /// </summary>
        /// <returns></returns>
        public static ShipperEntity FetchShipperEntity(OrderEntity Order)
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            Order.Shipper = (ShipperEntity) adapter.FetchNewEntity(new ShipperEntityFactory(), Order.GetRelationInfoShipper());
            return Order.Shipper;
        }
        
        /// <summary>
        /// ManyToOne
        /// Returns one instance of the entity 'Shipper' which are directly related to the instance of the entity 'Order' where Order.ShipVia=Shipper.ShipperID
        /// </summary>
        /// <returns></returns>
        public static ShipperEntity FetchShipperEnity(System.Int32 ShipVia)
        {
                DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
                ShipperEntity Shipper = new ShipperEntity(ShipVia);
                
                adapter.FetchEntity(Shipper);
    
                return Shipper;
        }


    
        /// <summary>
        /// ManyToMany
        /// Returns a typed collection with one or more instances of the entity 'Product' which are indirectly related to the instance of the entity 'Order' via a relation with the intermediate entity 'OrderDetails'.
        /// </summary>
        /// <returns></returns>
        public static EntityCollection FetchProductCollection(OrderEntity Order)
        {
            DataAccessAdapter adapter = new DataAccessAdapter(ConnectionString);
            EntityCollection ProductCollection = Order.Product;
            SortExpression ProductSorter = new SortExpression(SortClauseFactory.Create(ProductFieldIndex.ProductID, SortOperator.Ascending));
                
            adapter.FetchEntityCollection(ProductCollection,Order.GetRelationInfoProduct(),0,ProductSorter);
            return ProductCollection;
        }
        
    
    }
}






wayne avatar
wayne
User
Posts: 611
Joined: 07-Apr-2004
# Posted on: 12-Jul-2004 14:01:30   

Hi, it seems like you have been waiting for a repley for quite a while...

I would love to take a look at your code but at the moment i don't have the time and another obsticale is that it seems like you use adapter - I have no experience with the adapter model - so i will not be able to give any creditable critque, have to leave this to the other achitec boffins...

Posts: 497
Joined: 08-Apr-2004
# Posted on: 13-Jul-2004 14:54:33   

Hi there!

I did see this, and I will take a proper look soon, but I am also V. busy!!

bertcord avatar
bertcord
User
Posts: 206
Joined: 01-Dec-2003
# Posted on: 13-Jul-2004 21:39:19   

thanks guys... I have actaully changed it a bit... I will post the codesmith templtes here soon

Posts: 497
Joined: 08-Apr-2004
# Posted on: 14-Jul-2004 00:47:05   

Hi again.

Finally read through the code and description...

Short answer: Yep, seems good sunglasses

Long answer: What you are doing is kind of creating a wrapper for the generated code, which becomes your BL "controller". It all looks kind-of familiar to me, and seems ok - the big question is does it make your life easier in the PL? If it does, and it looks like it does, then it must be working simple_smile Its a similar approach to ours - we have methods in our BL classes that "do something" related to the business process - ListAll, ListBySite, GetByID, Save etc etc. It nicely encapsulates common chunks of code into methods that the PL can call.

I see you have lots of overloads for getting the orders - this is something I have mulled over quite a bit - how best to allow the BL to get a collection of something with a variety of filters. At the moment we have lots of overloads too, but I wonder if there are other ways.... Microsoft have a CRM product and in their architecture document they defined an XML syntax for defining filters (called FETCH) - very interesting! You could implement a fetch decoder that created LLBL predicates, that would be very powerful!

One question: Who consumes you "Controller"? I notice that you accept an Ipredicate to one of the methods, do you allow the PL to define a predicate and call the Fetch method? This is something we have avoided at all costs, the PL knows nothing about LLBLGen, it just asks the BL whe it wants something.

  1. At first I thought about subclassesing the Dataaccess adapter object...but I when I started thinking about the save logic I decided against it. I want the ability to support transactions so I am going to create overloads for the saves that has an adapter object as a parameter.

This sounds fine to me! Its also similar to what we have, don't see much advanatge to subclassing the dataacessadaptor.

  1. Many of the apps I am working on are simple reporting types aps...the majority of the time I need a default sort. In my controller class I create a DefaultOrderSorter object that is used in many of my FetchOrderCollection methods. IS this the correct way to do this?

We have default (or common) filters that are defined in a similar way. Its simple, and it works...

Another question: What do you do if your BL needs to pass back a single order, but your PL needs an extra bit of information about the order that might not be stored in the database table - kind of a "custom field"? E.g. You also want to know the order total, and you want this with the order thats returned with "FetchOrderController" or whatever it was.. This problem caused us to create "custom entities"... but thats another post ...

Hope this is useful!

bertcord avatar
bertcord
User
Posts: 206
Joined: 01-Dec-2003
# Posted on: 14-Jul-2004 08:29:18   

Thanks for the feedback. I finished the Entity Saves…now I just need to finish the concurrency and GUI stuff

MattWoberts wrote:

the big question is does it make your life easier in the PL? If it does, and it looks like it does, then it must be working simple_smile Its a similar approach to ours - we have methods in our BL classes that "do something" related to the business process - ListAll, ListBySite, GetByID, Save etc etc. It nicely encapsulates common chunks of code into methods that the PL can call.

life easier...well not yet but I thnk it will I have just started my firstapplication using this code simple_smile The nice thing about it is the entire class above was generated with a template.. makes it very easy to start the fun coding.

MattWoberts wrote:

I see you have lots of overloads for getting the orders - this is something I have mulled over quite a bit - how best to allow the BL to get a collection of something with a variety of filters.

I based all of my Fetches on the adapter object. The base adapter object has very similar overloads for the FetchCollection. I wasnt sure what I needed so I figured I woudl duplicate the adapter fetches.

MattWoberts wrote:

One question: Who consumes you "Controller"? I notice that you accept an Ipredicate to one of the methods, do you allow the PL to define a predicate and call the Fetch method? This is something we have avoided at all costs, the PL knows nothing about LLBLGen, it just asks the BL whe it wants something.

I left that one in their just in case I need to filter on another field…. The template will only generate filter methods for foreign key related objects… so If I wanted to fetch an orderCollection with a certain value I would need this method. But I do not plan on creating the predicate in my PL. If I needed to search like in the preceding example I would create another method in the BL to do this.

MattWoberts wrote:

Another question: What do you do if your BL needs to pass back a single order, but your PL needs an extra bit of information about the order that might not be stored in the database table - kind of a "custom field"? E.g. You also want to know the order total, and you want this with the order thats returned with "FetchOrderController" or whatever it was.. This problem caused us to create "custom entities"... but thats another post ..

I haven’t run into this yet…well actually I did…my UserEntity I wanted to display LastName, Firstname. For this I just edited the User Entity and added a read only property that appended the two fields

agian thanks for the feeback this has been a great learning experience. Bert

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 22-Jul-2004 17:03:30   

Hmmm, smells like a DNN -> LLBLGen provider model in the works

bertcord avatar
bertcord
User
Posts: 206
Joined: 01-Dec-2003
# Posted on: 23-Jul-2004 06:42:41   

Devildog74 wrote:

Hmmm, smells like a DNN -> LLBLGen provider model in the works

hum that sounds like a good idea....