HiveBrain v1.2.0
Get Started
← Back to all entries
debugcsharpMinor

Linq-to-Sage: CRUD Operations

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
sagelinqoperationscrud

Problem

Following-up on my Linq-to-Sage implementation, I ended up implementing all CRUD operations, following the Sage 300 view protocols.

So, the entities derive from classes that look like this:

namespace SageAPI.Views.PurchaseOrders
{
    /// 
    /// Defines view and key field mappings for 'Purchase Orders' view.
    /// 
    [MapsTo("PO0620")]
    public class PO0620
    {
        [Key(KeyType.GeneratedByView)]
        [MapsTo("PORHSEQ")]
        public decimal Key { get; set; }
   }
}


The KeyType enum determines how keys are generated:

public enum KeyType
{
    /// 
    /// Indicates that the key value is specified manually.
    /// 
    Manual,
    /// 
    /// Indicates that the key value is handled by the view, like an identity column.
    /// 
    GeneratedByView,
    /// 
    /// Indicates that the key value is handled through composition.
    /// Use for a foreign key column referring to the parent view in a composite-key setup.
    /// 
    GeneratedByHeader
}


...Which determines how the API is being used for inserting/updating these views. So I added 2 static helper methods in the ViewSet class:

private static bool HasAutomaticKey(TEntity entity)
{
    return GetKeys(entity).Any(key => key.KeyType == KeyType.GeneratedByView || key.KeyType == KeyType.GeneratedByHeader);
}

private static IEnumerable> GetKeys(TEntity entity)
{
    return entity.GetPropertyInfos().Where(property => property.KeyType != null);
}


The WriteKeys and WriteEntity methods are used for writing to the active record:

```
private void WriteEntity(IEnumerable> properties)
{
foreach (var property in properties)
{
try
{
View.Fields.FieldByName(property.FieldName).SetValue(property.Value, false);
}
catch (COMException exception)
{
var error = View.Parent.Parent.Errors[0];
throw new SessionErrorException(error.Message, exception);
}
}
}

private void WriteKeys(T

Solution

If you're unsure whether the API is going to throw a ViewException or a COMException, or under which circumstances it's going to throw which, your exception handling has holes.

And it's not very DRY.

Instead of repeating this block everywhere (well, you're not repeating it everywhere, but it's not clear whether that's intended):

catch (ViewException exception)
{
    var error = View.Parent.Parent.Errors[0];
    throw new SessionErrorException(error.Message, exception.InnerException);
}
catch (COMException exception)
{
    var error = View.Parent.Parent.Errors[0];
    throw new SessionErrorException(error.Message, exception);
}


Consider replacing it with a shorter, Pokemon catch block:

catch (Exception exception)
{
    OnSessionErrorException(exception);
    throw;
}


And then have that OnSessionErrorException method centralize exception handling:

private void OnSessionErrorException(Exception exception)
{
    var session = View.Parent.Parent;
    var sessionError = session.Errors.Count > 0
        ? session.Errors[0]
        : null;

    var message = sessionError == null ? exception.Message : sessionError.Message;
    if (exception is ViewException)
    {
        throw new SessionErrorException(message, exception.InnerException);
    }

    throw new SessionErrorException(message, exception);
}


That way you'll be throwing a SessionErrorException with the correct inner exception and message everytime, with minimal code repetition.

Since it's a "catch 'em all" handler in a ViewSet class, perhaps SessionErrorException would be better off with a less specific ViewSetException type name; the handler could then be renamed OnViewSetException.

The dual try blocks in BeginInsert, make it harder to read than necessary. Just wrap the whole block in a single try block instead:

public void BeginInsert(TEntity entity)
{
    try
    {
        if (HasAutomaticKey(entity))
        {
            View.RecordCreate(ViewRecordCreate.DelayKey);
        }
        else
        {
            View.RecordClear();
        }

        var properties = entity.GetPropertyInfos().Where(property =>
            property.ViewName == View.ViewID
            && (property.KeyType == null || property.KeyType == KeyType.Manual)
            && property.EditMode != EditMode.ReadOnly);

        WriteEntity(properties);
    }
    catch (Exception exception)
    {
        OnViewSetException(exception);
        throw;
    }
}

Code Snippets

catch (ViewException exception)
{
    var error = View.Parent.Parent.Errors[0];
    throw new SessionErrorException(error.Message, exception.InnerException);
}
catch (COMException exception)
{
    var error = View.Parent.Parent.Errors[0];
    throw new SessionErrorException(error.Message, exception);
}
catch (Exception exception)
{
    OnSessionErrorException(exception);
    throw;
}
private void OnSessionErrorException(Exception exception)
{
    var session = View.Parent.Parent;
    var sessionError = session.Errors.Count > 0
        ? session.Errors[0]
        : null;

    var message = sessionError == null ? exception.Message : sessionError.Message;
    if (exception is ViewException)
    {
        throw new SessionErrorException(message, exception.InnerException);
    }

    throw new SessionErrorException(message, exception);
}
public void BeginInsert(TEntity entity)
{
    try
    {
        if (HasAutomaticKey(entity))
        {
            View.RecordCreate(ViewRecordCreate.DelayKey);
        }
        else
        {
            View.RecordClear();
        }

        var properties = entity.GetPropertyInfos().Where(property =>
            property.ViewName == View.ViewID
            && (property.KeyType == null || property.KeyType == KeyType.Manual)
            && property.EditMode != EditMode.ReadOnly);

        WriteEntity(properties);
    }
    catch (Exception exception)
    {
        OnViewSetException(exception);
        throw;
    }
}

Context

StackExchange Code Review Q#119675, answer score: 2

Revisions (0)

No revisions yet.