Related collection and totals in GUI

Posts   
 
    
wexa
User
Posts: 38
Joined: 27-Jul-2007
# Posted on: 19-Mar-2008 17:36:05   

Hi, I have been dealing with a master detail form, I have seen some examples but still I am getting a hard work.

I have a scenario like Order and Order Details, what I do is to load an order in the form and then I filled a gridview with the details.

In the grid a user can add/remove order details

On the order I have a TOTAL, and TAX field that needs to be updated everytime a user adds/remove a detail.

What would be the best approach to do this??

I used databind of the textboxes and loaded the related collection with prefetch. After I add a new row to the ORDER_DETAILS grid, on the ValidateRow event I save the detail to the DB but I have to recompute the TOTAL and TAX field on the ORDER. This is the same for deleting.

Is there a way to put code on the ORDER entity so it can recompute the order_total, or what would be the best way to do this? Actually I do it by hand on the UI, but I think there can be a bug, since I am recomputing TOTALS after each add/delete of ORDER, but if for some reason the connection is broken I may lost the status.

I am using ADAPTER and Windows forms

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 20-Mar-2008 04:01:30   

I see these options:

A. Subscribe a new event handler for yourOrder.OrderDetails.EntityAdded and ...EntityRemoved at the constructor of the OrderEntity (you could do that in a partial class or user custom code). At that event handler call to a method that recalculate the fields.

B. In a business rules class (f.i. OrderManager, create a method AddOrderDetail that do that logic.

C. Do that at your GUI (like you are doing).

In all options you should save the two entities in a transaction. You could do that saving recursively either the OrdenEntity or OrderDetail (is better the OrderEntity if you have added many details). You could also start a transaction, then add the order and save it (no recursively), then add the detail and save it (no recursively); finally commit the transaction.

David Elizondo | LLBLGen Support Team
wexa
User
Posts: 38
Joined: 27-Jul-2007
# Posted on: 20-Mar-2008 04:22:29   

As an update after working on this I have modified and used Unitofwork and worked ok when adding new registers to the Order.

This is for adding new details to the main entity. Now I have this problem:

For the same entity (ORDER) I have 3 collections (Details, References and Documents). References and documents are independent registers that have a field with the orderId, that way is how I relate them.

So I have a Windows Form with the ORDER and it has 3 grids with the Details, References and Documents collections. User can add/remove any element from the collection.

Elements are not deleted, they are only "unreferenced", I mean that I set the orderId =0 so I can load that element in another ORDER.

I also have to sum some fields from the related collection and set fields on the ORDER, then save the ORDER with the right data.

When I open the form I fetch the order and related entities using this:


       public int Id = 0;
         OrderEntity order = new OrderEntity();
        DataAccessAdapter adapter = new DataAccessAdapter();
        UnitOfWork2 uow = new UnitOfWork2();
 public void LoadOrder()
        {
            if (orderId > 0) order.Id = orderId;
            //// Load relate entities
            IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.OrderEntity);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathDetails);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathReferences);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathDocuments);
            adapter.FetchEntity(order, prefetchPath);

// ASIGN SOURCES
            _gridDetails.DataSource = order.Details;
            _gridReferences.DataSource = order.References;
            _gridDocuments.DataSource = order.Documents;


            uow.AddForSave(liq, true);
        }

  public void LoadDependencies()
        {
// RESET COLLECTIONS
            if(order.Details.Count>0)
            {
                order.Details.Clear();
            }
            if (order.References.Count > 0)
            {
                order.References.Clear();
            }

            if (order.Documents.Count > 0)
            {
                order.Documents.Clear();
            }
    //// Load relate entities
            IPrefetchPath2 prefetchPath = new PrefetchPath2((int)EntityType.OrderEntity);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathDetails);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathReferences);
            prefetchPath.Add(LiquidacionEntity.PrefetchPathDocuments);
            adapter.FetchEntity(order, prefetchPath);

// ASIGN SOURCES
            _gridDetails.DataSource = order.Details;
            _gridReferences.DataSource = order.References;
            _gridDocuments.DataSource = order.Documents;
        }


The above code loads the Order and the 3 collections, but here comes my problem. I have a popup form where I display the References (they already exist in database) that are not related to an order, and the user selects wich References they want to relate, so I do this:


 private void _gridReferences_EmbeddedNavigator_ButtonClick(object sender, DevExpress.XtraEditors.NavigatorButtonClickEventArgs e)
        {
            if (e.Button.ButtonType == DevExpress.XtraEditors.NavigatorButtonType.Append)
            {
                CustomControls.referenceSelect selectReference = new CustomControls.referenceSelect();
// CUSTOM FILTER TO LOAD THE DATA ON THE POPUP
                IRelationPredicateBucket bucket = new RelationPredicateBucket();
                bucket.PredicateExpression.Add(Reference.Order < 1);
                selectReference .filter= bucket;
                selectReference .LoadReferences();
                selectReference .ShowDialog();
                OrderReference referenc = selectReference .referenceFocused; // THIS RETURNS A FOCUSED ELEMENT FROM THE POPUP FORM
                if (referen != null)
                {
                    referen.order = order.Id;
                    adapter.SaveEntity(referen, true);
                    //liq.Anticipo.Add(anticipo); 
                    UpdateTotals(); // SUMS ONE FIELD FROM REFERENCES AND THE AGGREGATE IS ASSIGNED TO A FIELD IN THE ORDER
                    LoadDependencies();
                }

            }


// FOR REMOVE A REFERENCE FROM THE GRID (NOT DELETE FROM DB, ONLY REMOVE REFERENCE I DO THIS
            if (e.Button.ButtonType == DevExpress.XtraEditors.NavigatorButtonType.Remove)
            {
                OrderReference referenc= (OrderReference )gridViewAnticipos.GetRow(gridReferences.FocusedRowHandle);
                referenc.order= 0;
                adapter.SaveEntity(referenc);
                UpdateTotals();
                LoadDependencies();
            }
        }


This works ok in database, it is refreshing the data but the grid acts strange, sometimes it displays only one row after removing one entity from the collection, and other times when I add one it adds several rows, so I dont know if I am doing the things in the right way.

Regards

wexa
User
Posts: 38
Joined: 27-Jul-2007
# Posted on: 20-Mar-2008 05:10:34   

Thank you for your response daelmo I was updating while you answered with your help, so I did not see your response, let me try that, anyway I did put some code and explained what I was doing since I have spent a lot of time dealing with this.

Will try to check the options you mentioned above, regards.

wexa
User
Posts: 38
Joined: 27-Jul-2007
# Posted on: 20-Mar-2008 07:35:04   

This is the first time I make a BL, I have been reading help and some threads, also I downloaded the HnD and checked how the BL was made.

I made this in a separate BL Project called OrderManager and have:


 public static bool AddReferenceToOrder(OrderEntity order, ReferenceEntity referen)
            {
                if (reference.Order > 0) // Its already asigned
                {
                    return false;
                }

                DataAccessAdapter adapter = new DataAccessAdapter();
                adapter.StartTransaction(IsolationLevel.ReadCommitted, "AddOrder");
                try
                {
                    // Update Totals
                    order.TotalReferences -= referen.Total;
                    order.GrandTotal += referen.Total; // References are charges
                    referen.Order= liquidacion.Id;

                    // save the two entities.
                    adapter.SaveEntity(referen);
                    adapter.SaveEntity(order);

                    // done
                    adapter.Commit();
                    return true;
                }
                catch
                {
                    // abort, roll back the transaction
                    adapter.Rollback();
                    // bubble up exception
                    throw;
                }
                finally
                {
                    // clean up. Necessary action.
                    adapter.Dispose();
                }
            }

I have some questions:

1) In the Hnd you used mainly the entity Id as parameters (I think because it is a web app). Is it right to use entities instead??

2) In my UI (WinForm) I have this:

 if (e.Button.ButtonType == DevExpress.XtraEditors.NavigatorButtonType.Append)
            {
                CustomControls.referenceSelect selectReference = new CustomControls.referenceSelect();
// CUSTOM FILTER TO LOAD THE DATA ON THE POPUP
                IRelationPredicateBucket bucket = new RelationPredicateBucket();
                bucket.PredicateExpression.Add(Reference.Order < 1);
                selectReference .filter= bucket;
                selectReference .LoadReferences();
                selectReference .ShowDialog();
                OrderReference referenc = selectReference .referenceFocused; // THIS RETURNS A FOCUSED ELEMENT FROM THE POPUP FORM
                if (referen != null)
                {
                   OrderManager.AddReferenceToOrder(order,referen);
                }

            }

Do I have to refetch everything and update fields or how do you update the UI ??

Regards

Walaa avatar
Walaa
Support Team
Posts: 14950
Joined: 21-Aug-2005
# Posted on: 20-Mar-2008 10:05:07   

I think you could do the following too:

1- Create a property called Total in the Order entity, which does the following:

Get
{
int total = 0;
foreach (OrderDetailEntity detail in OrderDetails)
{
total+= (detail.UnitPrice * detail.Quantity);
}
}

Then if you are databinding the Order entity, all you have to do when you add a new orderDetail to its details collection or when you update an existing detail is to re-bind the Order Entity to its bound control. This will read the value of the Total property again which will compute the total of all the current related detail entities.

A similar approach was done in the WebDatabinding example project in our examples section.

wexa
User
Posts: 38
Joined: 27-Jul-2007
# Posted on: 25-Mar-2008 03:50:58   

Thank you very much, it is working perfect and I have speed up the development process after your recomendations because I was doing by hand everything. Now it is ok.

I have a 2 questions: 1)Is it recommended to have a total field in orders? or do you always compute using the Virtual property?

2) I have added a property to costumer called FULLNAME trough a partial class, how can I in an order access this property? I already have related firstName, LastName in designer, do I have to recreate a FULLNAME property for the Order or can I access the FULLNAME in the costumer entity?

FULLNAME for costumer is a virtual property in an extended class.

Regards.... by the way llblgen is excellent, everyday we discover new ways to use it, we are still learning to use but is very good.

daelmo avatar
daelmo
Support Team
Posts: 8245
Joined: 28-Nov-2005
# Posted on: 25-Mar-2008 07:02:04   

wexa wrote:

1)Is it recommended to have a total field in orders? or do you always compute using the Virtual property?

Normalization rules don't recommend to have computed columns at DB, that isn't always true but depends upon the project type and size. You can have a computed property at your OrderEntity, this property doesn't have to be virtual unless you want to override it later.

wexa wrote:

2) I have added a property to costumer called FULLNAME trough a partial class, how can I in an order access this property? I already have related firstName, LastName in designer, do I have to recreate a FULLNAME property for the Order or can I access the FULLNAME in the costumer entity?

You can simply add a property to a Customer partial class that concatenate your first and last name:

public string FullName
{
     get {
          return (FirstName + " " + LastName);
     }
}

Then you can access via the someOrder.Customer.FullName property.

wexa wrote:

Regards.... by the way llblgen is excellent, everyday we discover new ways to use it, we are still learning to use but is very good.

Good wink .

David Elizondo | LLBLGen Support Team