Reviewing Mendz.Library.ResultInfo

In Working with Dapper (Part 2), I showed Mendz.Library.ResultInfo, a class which allows procedures to return a structured message that is rich with data and information. While testing scenarios with using ResultInfo, I found a flaw that needs to be fixed.

ResultInfo supports hierarchical messages to be gathered via ResultInfos property. In the original version, I defined it as a List<ResultInfo>. This design is actually OK, specially if you are calling sub-procedures directly inside your CUD methods, for example.

public Transaction Update(Transaction model, dynamic expansion, 
    out List<ResultInfo> returnValue)
{
    returnValue = new List<ResultInfo>();
    string spName1 = "TransactionUpdate";
    ... prepare parameters ...
    int affectedCount = DbDataContext.Context.ExecuteQuery(spName1, ...);
    ResultInfo resultinfo = new ResultInfo(spName1, affectedCount);
    List<ResultInfo> rv = new List<ResultInfo>();
    string spName2 = "TransactionCommentCreate";
    foreach (var comment in model.TransactionComments)
    {
        ... prepare parameters ...
        affectedCount = DbDataContext.Context.ExecuteQuery(spName2, ...);
        comment.ID = p.Get("@id");
        rv.Add(new ResultInfo(spName2, affectedCount));
    }
    resultInfo.ResultInfos = rv;
    returnValue.Add(resultInfo);
    return model;
}

However, if the sub-procedure "TransactionCommentCreate" is already implemented in a repository, you ought to re-use it. TransactionComment's Create() method can be defined as follows:

public TransactionComment Create(TransactionComment model, dynamic expansion, 
    out List<ResultInfo> returnValue)
{
    returnValue = new List<ResultInfo>();
    string spName = "TransactionCommentCreate";
    ... prepare parameters ...
    int affectedCount = DbDataContext.Context.ExecuteQuery(spName, ...);
    model.ID = p.Get("@id");
    ResultInfo resultinfo = new ResultInfo(spName, affectedCount);
    returnValue.Add(resultInfo);
    return model;
}

Which can be called by the Transaction's Update() method as follows:

public Transaction Update(Transaction model, dynamic expansion, 
    out List<ResultInfo> returnValue)
{
    returnValue = new List<ResultInfo>();
    string spName = "TransactionUpdate";
    ... prepare parameters ...
    int affectedCount = DbDataContext.Context.ExecuteQuery(spName1, ...);
    ResultInfo resultinfo = new ResultInfo(spName, affectedCount);
    using (TransactionCommentRepository tcr = 
        new TransactionCommentRepository(DbDataContext))
    {
        foreach (var comment in model.TransactionComments)
        {
            comment = tcr.Create(comment, null, out List<ResultInfo> rv);
            resultInfo.ResultInfos = rv;
        }
    }
    returnValue.Add(resultInfo);
    return model;
}

Do you see the problem? Exactly! Every loop overwrites resultInfo.ResultInfos = rv! I can probably work around it via foreach (var ri in rv) { subrv.Add(ri); }, for example. However, the fact that it's a workaround maintains that there is a problem.

The CRUDS interface methods all have out List<ResultInfo> returnValue parameter. With that signature everywhere, it is easier to understand that ResultInfo.ResultInfos should preserve the sub-repository's out parameter as-is, specially how the sub-repository built its own List<ResultInfo> heirarchy. This makes planning for how ResultInfo.ResultInfos should be "read" more predictable.

Thus, the new version now defines ResultInfo.ResultInfos as List<List<ResultInfo>>. With this simple change, you can now simply do the following to handle a sub-repository's return value:

    ...
    using (TransactionCommentRepository tcr = 
        new TransactionCommentRepository(DbDataContext))
    {
        foreach (var comment in model.TransactionComments)
        {
            comment = tcr.Create(comment, null, out List<ResultInfo> rv);
            resultInfo.ResultInfos.Add(rv);
        }
    }
    ...

As you can see, the sub-repository's return value can now be relayed as-is. The code also looks cleaner and more expressive. With this change, CUDs that call sub-procedures directly should also be adjusted.

public Transaction Update(Transaction model, dynamic expansion, 
    out List<ResultInfo> returnValue)
{
    returnValue = new List<ResultInfo>();
    string spName1 = "TransactionUpdate";
    ... prepare parameters ...
    int affectedCount = DbDataContext.Context.ExecuteQuery(spName1, ...);
    ResultInfo resultinfo = new ResultInfo(spName1, affectedCount);
    string spName2 = "TransactionCommentCreate";
    foreach (var comment in model.TransactionComments)
    {
        // move inside the loop
        List<ResultInfo> rv = new List<ResultInfo>();
        ... prepare parameters ...
        affectedCount = DbDataContext.Context.ExecuteQuery(spName2, ...);
        comment.ID = p.Get("@id");
        rv.Add(new ResultInfo(spName2, affectedCount));
        // move inside the loop
        resultInfo.ResultInfos.Add(rv);
    }
    returnValue.Add(resultInfo);
    return model;
}

The change is clumsy, but the resulting ResultInfo.ResultInfos will look the same as how it would be if the sub-procedure is in a sub-repository instead. The consistency is enforced by design, which is more important, specially if you want to be able to build a common way to parse out List<ResultInfo> returnValue in your application.

Here's the new ResultInfo code.

ResultInfo.cs
using System.Collections.Generic;

namespace Mendz.Library
{
    public class ResultInfo
    {
        public string SourceName { get; set; }

        public Dictionary<string, object> InputValues { get; set; }

        public Dictionary<string, object> OutputValues { get; set; }

        public int AffectedCount { get; set; }

        public List<List<ResultInfo>> ResultInfos { get; set; } = 
            new List<List<ResultInfo>>();

        public ResultInfo(string sourceName, 
            Dictionary<string, object> inputValues, 
            Dictionary<string, object> outputValues, 
            int affectedCount = 0)
        {
            SourceName = sourceName;
            InputValues = inputValues;
            OutputValues = outputValues;
            AffectedCount = affectedCount;
        }

        public ResultInfo(string sourceName, 
            Dictionary<string, object> outputValues, 
            int affectedCount = 0)
            : this(sourceName, null, outputValues, affectedCount)
        {
        }

        public ResultInfo(string sourceName, int affectedCount)
            : this(sourceName, null, null, affectedCount)
        {
        }
    }
}

Comments