Posts

....
Technical Blog for .NET Developers ©

Tuesday, October 2, 2018

Stopwatch of System.Diagnostics

For the sake of performance, the response times of many processes should be tested

The namespace System.Diagnostics exposes diverse classes for this purpose, one of them is the class StopWatch, this class represents a stopwatch to measure the time delay of the synchronous processes

We can browse the code up to its definition, exposed below

     
    public class Stopwatch
    {
        public static readonly long Frequency;
        public static readonly bool IsHighResolution;
        public Stopwatch();
        public TimeSpan Elapsed { get; }
        public long ElapsedMilliseconds { get; }
        public long ElapsedTicks { get; }
        public bool IsRunning { get; }
        public static long GetTimestamp();
        public void Reset();
        public void Restart();
        public void Start();
        public static Stopwatch StartNew();
        public void Stop();
    }
    


In this example we are working on an Oracle database invoking a stored procedure to update all records in a table, and we need to know with high precission how much time it delays when it´s called from our web site

We have data of our Contacts and the next stored procs to retrieve and update the whole table

     
  create or replace
  PROCEDURE P_SELECT_CONTACTS
    (RES OUT PKG_CONTACTS.CURSOR_ALL)
  IS
  BEGIN
  
    OPEN RES FOR SELECT FirstName "First Name", LastName "Last Name", 
      Phone "Phone", EMail "E Mail", PreferredContactForm "Preferred Contact Form" 
    FROM C_CONTACTS
    ORDER BY FirstName, LastName;
    
    RETURN;
  
  END P_SELECT_CONTACTS;
  
  --
  
  create or replace
  PROCEDURE P_UPDATE_CONTACT_FORM
    (v_ContactForm C_CONTACTS.PreferredContactForm%TYPE,
     v_RecordsAffected OUT NUMBER)
  IS
  BEGIN
  
    UPDATE C_CONTACTS SET PreferredContactForm = v_ContactForm;
    v_RecordsAffected := sql%rowcount;
    
  END P_UPDATE_CONTACT_FORM;


In our web site this is the display



Now we update the table through the next code

     
using (connection = new OracleConnection(ConfigurationManager.ConnectionStrings["csMethod"].ConnectionString))
{
    System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();
    stopWatch.Start();

    connection.Open();
    OracleCommand command = connection.CreateCommand();
    command.CommandType = CommandType.StoredProcedure;
    command.CommandText = "P_UPDATE_CONTACT_FORM";
    OracleParameter pContactForm = new OracleParameter("v_ContactForm", OracleType.VarChar);
    pContactForm.Direction = ParameterDirection.Input;
    pContactForm.Value = ddContactForm.SelectedValue;
    OracleParameter pRecordsAffected = new OracleParameter("v_RecordsAffected", 
            OracleType.Number);
    pRecordsAffected.Direction = ParameterDirection.Output;

    command.Parameters.AddRange(new OracleParameter[] { pContactForm, pRecordsAffected });
    command.ExecuteNonQuery();

    Int64 recordsAffected = Convert.ToInt64(pRecordsAffected.Value);

    lblResult.Text = recordsAffected.ToString() + " records affected";

    stopWatch.Stop();
    string elapsedTime = stopWatch.Elapsed.ToString(@"mm\:ss\.ffff");

    lblStopWatch.Text = "Elapsed time: " + elapsedTime;
}


And the result is the next



As you can see, we have a measurement of round-trip time very accurate

<METHOD SOFTWARE © 2012>

Friday, August 17, 2018

Oracle Transactions

When we have a web application with an open connection to a database, in this case Oracle 11G, we have to improve the calls to the db server, and keep the ACID rules (Atomicity, Consistence, Isolation, and Durability) of the database

We have the next scenario, a web form for sumbit a new customer of our book store and all his preferences marked

In our SQL Developer, this is the schema



and the code neccessary for accomplish this task is as follows

     
create or replace
PACKAGE PKG_BOOKSTORE AS
  TYPE CURSOR_ALL IS REF CURSOR;
  PROCEDURE P_SELECT_GENRES;
  PROCEDURE NEW_CUSTOMER;
  PROCEDURE NEW_PREFERENCE;
END;

-- 

create or replace
PROCEDURE P_SELECT_GENRES
  (RES OUT PKG_BOOKSTORE.CURSOR_ALL)
IS
BEGIN

  OPEN RES FOR SELECT IdGenre, Genre FROM T_GENRES;
  
  RETURN;

END P_SELECT_GENRES;

--

create or replace
PROCEDURE NEW_CUSTOMER
  (v_FirstName IN T_CUSTOMERS.FirstName%TYPE, 
   v_LastName IN T_CUSTOMERS.LastName%TYPE,
   v_IdCustomer OUT T_CUSTOMERS.IdCustomer%TYPE)
IS BEGIN

  SELECT sqCustomers.NextVal INTO v_IdCustomer FROM DUAL;
  
  INSERT INTO T_CUSTOMERS VALUES (v_IdCustomer, v_FirstName, v_LastName);
  EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
     RAISE_APPLICATION_ERROR(SQLCODE, 'Cannot insert duplicate value - ' + SQLERRM);
    WHEN OTHERS THEN
     RAISE_APPLICATION_ERROR(SQLCODE, SQLERRM);

END NEW_CUSTOMER;

-- 

create or replace
PROCEDURE NEW_PREFERENCE
  (v_IdCustomer IN T_CUSTOMERS.IdCustomer%TYPE, v_IdGenre IN T_GENRES.IdGenre%TYPE)
IS BEGIN

  INSERT INTO T_CUSTOMERS_GENRES VALUES (v_IdCustomer, v_IdGenre);

  EXCEPTION
    WHEN DUP_VAL_ON_INDEX THEN
     RAISE_APPLICATION_ERROR(SQLCODE, 'Cannot insert duplicate value - ' + SQLERRM);
    WHEN OTHERS THEN
     RAISE_APPLICATION_ERROR(SQLCODE, SQLERRM);
      
END NEW_PREFERENCE;


Now we include code to display all genres and get ready the form to submit a new customer

     
    private void displayGenres()
    {
        try
        {
            OracleDataReader drGenres = getGenres();

            while (drGenres.Read())
            {
                clbGenres.Items.Add(new ListItem() 
                  { Value = drGenres["IdGenre"].ToString(), 
                    Text = drGenres["Genre"].ToString() 
                  });
            }
        }
        catch (OracleException oex)
        {
            Response.Redirect("error.aspx?desc=" + oex.Message);
        }
        finally
        {
            if (connection.State == ConnectionState.Open)
                connection.Close();
        }
    }

    private OracleDataReader getGenres()
    {
        connection.Open();
        OracleCommand command = connection.CreateCommand();
        command.CommandType = CommandType.StoredProcedure;
        command.CommandText = "P_SELECT_GENRES";
        OracleDataReader dr;
        OracleParameter pRes = new OracleParameter("RES", OracleType.Cursor);
        pRes.Direction = ParameterDirection.Output;

        command.Parameters.Add(pRes);

        dr = command.ExecuteReader(CommandBehavior.SingleResult);

        return dr;
    }


and we get this result set



The next step is adding the code to call the stored procs and stablish control over the transaction

     
    private void insertCustomer()
    {
        connection.Open();
        OracleCommand cmdCustomer = connection.CreateCommand();
        OracleTransaction trax_new_customer = 
           connection.BeginTransaction(IsolationLevel.Serializable);

        try
        {
            cmdCustomer.Transaction = trax_new_customer;
            cmdCustomer.CommandType = CommandType.StoredProcedure;
            cmdCustomer.CommandText = "NEW_CUSTOMER";

            cmdCustomer.Parameters.Add(new OracleParameter
               ("v_FirstName", txtFirstName.Text));
            cmdCustomer.Parameters.Add(new OracleParameter
               ("v_LastName", txtLastName.Text));

            OracleParameter v_IdCustomer = new OracleParameter
               ("v_IdCustomer", OracleType.Int32);
            v_IdCustomer.Direction = ParameterDirection.Output;

            cmdCustomer.Parameters.Add(v_IdCustomer);

            cmdCustomer.ExecuteNonQuery();

            int IdCustomer = (int)v_IdCustomer.Value;                

            foreach (ListItem genre in clbGenres.Items)
            {
                if (genre.Selected)
                {
                    OracleCommand cmdPreference = connection.CreateCommand();

                    cmdPreference.Transaction = trax_new_customer;
                    cmdPreference.CommandType = CommandType.StoredProcedure;
                    cmdPreference.CommandText = "NEW_PREFERENCE";

                    cmdPreference.Parameters.Add(new OracleParameter
                      ("v_IdCustomer", IdCustomer));
                    cmdPreference.Parameters.Add(new OracleParameter
                      ("v_IdGenre", genre.Value));

                    cmdPreference.ExecuteNonQuery();
                }
            }

            trax_new_customer.Commit();
        }
        catch (Exception ex)
        {
            trax_new_customer.Rollback();
            Response.Redirect("error.aspx?desc=" + ex.Message);
        }
    }


The .NET Framework Data Provider for Oracle only supports ReadCommitted and Serializable isolation levels

The result with a correct execution is the following



<METHOD SOFTWARE © 2012>

Sunday, March 4, 2018

HTML5 Drag and Drop

HTML5 API includes Drag and Drop (DnD) native functionality

The event listener methods for all the drag and drop events accept Event object which has a readonly attribute called dataTransfer. The event.dataTransfer returns DataTransfer object associated with the event

This is the list of events fired during the different stages

Event Description
dragstart Fires when the user starts dragging of the object.
dragenter Fired when the mouse is first moved over the target element while a drag is occuring. A listener for this event should indicate whether a drop is allowed over this location. If there are no listeners, or the listeners perform no operations, then a drop is not allowed by default.
dragover This event is fired as the mouse is moved over an element when a drag is occuring. Much of the time, the operation that occurs during a listener will be the same as the dragenter event.
dragleave This event is fired when the mouse leaves an element while a drag is occuring. Listeners should remove any highlighting or insertion markers used for drop feedback.
drag Fires every time the mouse is moved while the object is being dragged.
drop The drop event is fired on the element where the drop was occured at the end of the drag operation. A listener would be responsible for retrieving the data being dragged and inserting it at the drop location.
dragend Fires when the user releases the mouse button while dragging an object.



In this post we develop an application to handle the drag and drop events between two elements, and launch a HttpPost method in the server which will ends inserting the dragged value in database

The first step is the definition of the UXinterface, in sequence the display is this



We are adding h5utils.js file, with an implementation of AddEvent function to simplify our code


var AddEvent = (function () {
    if (document.addEventListener) {
        return function (el, type, fn) {
            if (el && el.nodeName || el === window) {
                el.addEventListener(type, fn, false);
            } else if (el && el.length) {
                for (var i = 0; i < el.length; i++) {
                    AddEvent(el[i], type, fn);
                }
            }
        };
    } else {
        return function (el, type, fn) {
            if (el && el.nodeName || el === window) {
                el.attachEvent('on' + type, function () { return fn.call(el, window.event); });
            } else if (el && el.length) {
                for (var i = 0; i < el.length; i++) {
                    AddEvent(el[i], type, fn);
                }
            }
        };
    }
})();



Now the code to implement drag and drop events


    var pDragElement = document.createElement('p');

    var chemicalElements = document.querySelectorAll('div > p'), el = null;
    for (var i = 0; i < chemicalElements.length; i++) {

        el = chemicalElements[i];

        el.setAttribute('draggable', 'true');

        AddEvent(el, 'dragstart', dragStartElement);
        
        AddEvent(el, 'dragend', dragEndElement);        
    }

    function dragStartElement(e) {

        e.dataTransfer.effectAllowed = 'copy';
        e.dataTransfer.setData('Text', this.id);
        e.dataTransfer.setData('Type', this.innerHTML);
        
        this.style.backgroundColor = "#ffa31a";
    }
    
    function dragEndElement(e) {
        
        this.style.backgroundColor = "#fff9f0";
    }
    
    var divBoxElements = document.querySelector('#divBoxElements');

    AddEvent(divBoxElements, 'dragover', function (e) {

        if (e.preventDefault) e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
        return false;
    });

    AddEvent(divBoxElements, 'drop', function (e) {

        if (e.stopPropagation) e.stopPropagation();

        var element = e.dataTransfer.getData('Type');

        pDragElement.innerHTML = "Adding " + element + " element";

        var pClone = pDragElement.cloneNode(true);

        var newDiv = document.createElement("div");

        newDiv.appendChild(pClone);

        divBoxElements.appendChild(newDiv);

        InsertChemicalElement(element);

        return false;
    });



The function InsertChemicalElement will call the HttpPost server method


    function InsertChemicalElement(element) {
        
        var url = '@Url.Action("InsertChemicalElements", "Home")';

        $.post(url, { chemicalElement: element },
            
        function (data) {

            switch (data) {
                case 1:                    
                    divBoxElements.innerHTML = element + " inserted OK";
                    setTimeout(function() { divBoxElements.innerHTML = ""; }, 2000);
                    break;
                    
                default:
                    alert("Error inserting the element");
            }
        });
    }



The code in the server side makes use of EF, with one table and one stored procedure in the diagram



With a SOLID implementation, the code for inserting data is divided in two functions


    [HttpPost]
    public JsonResult InsertChemicalElements(string chemicalElement)
    {
        string[] elementData = chemicalElement.Split(':');

        string symbol = elementData[0].Trim();
        string name = elementData[1].Trim();

        int insertResult = _chemicalDatabase.InsertElement(symbol, name);

        return Json(insertResult);
    }


//////////


    public class ChemicalDatabase : IChemicalDatabase
    {
        public int InsertElement(string symbol, string name)
        {
            using (ChemicalsEntities entities = new ChemicalsEntities())
            {
                return entities.P_INSERT_ELEMENT(symbol, name);
            }
        }
    }