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.
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:
Here's the new ResultInfo code.
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
Post a Comment