How to transform Spec-flow table data into different values - c#

I need to transform Spec-flow table data that we get via table.CreateInstance() or table.CreateSet() . I am using Spec flow for DB testing and in some cases, Table field values needs to be mapped to different values as DB tables are storing codes instead of the the values we have entered in tables of feature files. I do not want to include the codes in feature files as it reduces the readability. For example, If I have entered Single for status as mentioned below, I want it to be mapped or transform to S in the data transfer object / POCO. What is the best approach ? Thanks in advance.
Given I entered the following data into the new account form:
| Name | Birthdate | Status |
| John Butcher| 2/2/1902 | Single |

I've started using "Test Models," which ended up being view models for my spec flow tests.
Using your example:
Given I entered the following data into the new account form:
| Name | Birthdate | Status |
| John Butcher| 2/2/1902 | Single |
The Domain Model:
public class Account
{
public string Name { get; set; }
public DateTime? Birthdate { get; set; }
public string Status { get; set; }
}
And the "Test Model":
public class AccountForm
{
public string Name { get; set; }
public DateTime? Birthdate { get; set; }
public string Status { get; set; }
public Account CreateInstance()
{
return new Account()
{
Name = Name,
Birthdate = Birthdate,
Status = Status.Substring(0, 1)
};
}
}
The step defintion:
[Given(#"Given I entered the following data into the new account form:")]
public void GivenIEnteredTheFollowingDataIntoTheNewAccountForm(Table table)
{
var form = table.CreateInstance<AccountForm>();
var account = form.CreateInstance();
// save to the database
}
For this one particular example it might be more than you need, but I've found this pattern works well when the data in the Scenario needs to be in a human readable format, which gets translated to a complex format in your Domain Model.

I'm not aware of anyway to do this automatically, so I can only think of two possibilities.
Don't use the CreateInstance or CreateSet methods and instead do all of the mapping manually, but encapsulate it in a [StepArgumentTransformation]
Use the methods you are using but then afterwards overwrite the automatically generated 'Single' values with 'S' after the instances have been created.

As Sam pointed out, we can use StepArgumentTransformarion or an approach similar to below. Add if else inside the extension methods if you want to map Value1 to Code1 for one type and Value1 to CodeX in another type using typeof(T).Name.Equals(typeof(yourtype).Name) as the condition
public static IEnumerable<T> CreateSetWithValueTransfer<T>(this Table table)
{
var mapper = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase)
{
{"Value1", "Code1"},
{"value2", "Code2"}
};
var set = ChangeValues(table, mapper);
return set.CreateSet<T>();
}
private static Table ChangeValues(Table table, Dictionary<string, string> mapper)
{
var mappedTable = new Table(table.Header.ToArray());
foreach (var row in table.Rows)
{
mappedTable.AddRow(row.Values.Select(x => mapper.ContainsKey(x) ? mapper[x] : x).ToArray());
}
return mappedTable;
}

I posed a similar question with a different example here. Seems like it should be possible to apply StepArgumentTransforms to CreateInstance/CreateSet, but just hasn't been implemented yet for basic types that already get transformed in the table conversion.
In your case (unlike mine), I think you could do it relatively easily with a custom ValueRetriever.

you can get values out of the table by
table.Rows.FirstOrDefault().Values.FirstOrDefault();

Related

Looping header and details records in same file

I have re edit this question below I have an example file which as multiple purchase orders in the file which is identified by the second column.
Order Number, Purchase Number,DATE,Item Code ,Qty, Description
1245456,98978,12/01/2019, 1545-878, 1,"Test"
1245456,98978,12/01/2019,1545-342,2,"Test"
1245456,98978,12/01/2019,1545-878,2,"Test"
1245456,98979,12/02/2019,1545-878,3,"Test 3"
1245456,98979,12/02/2019,1545-342,4,"Test 4"
1245456,98979,12/02/2019,1545-878,5,"Test 4"
What I want the end result to be is to be able to place the above into one class like the following
At the min I am using filelpers to parse the csv file this would work fine if I had sep header file and row file but they are combined as you see
var engine = new FileHelperEngine<CSVLines>();
var lines = engine.ReadFile(csvFileName);
So the Class should be like below
[DelimitedRecord(",")]
public class SalesOrderHeader
{
private Guid? _guid;
public Guid RowID
{
get
{
return _guid ?? (_guid = Guid.NewGuid()).GetValueOrDefault();
}
}
public string DocReference { get; set; }
public string CardCode { get; set; }
public string DocDate { get; set; }
public string ItemCode { get; set; }
public string Description { get; set; }
public string Qty { get; set; }
public string Price { get; set; }
[FieldHidden]
public List<SalesOrderHeader> OrdersLines { get; set; }
}
What I imagine I will have to do is two loops as you will see from my createsales order routine i first create the header and then add the lines in.
public void CreateSalesOrder(List<SalesOrderHeader> _salesOrders)
{
foreach (var record in _salesOrders.GroupBy(g => g.DocReference))
{
// Init the Order object
oOrder = (SAPbobsCOM.Documents)company.GetBusinessObject(SAPbobsCOM.BoObjectTypes.oOrders);
SAPbobsCOM.SBObob oBob;
// set properties of the Order object
// oOrder.NumAtCard = record.Where(w=>w.RowID = record.Where()
oOrder.CardCode = record.First().CardCode;
oOrder.DocDueDate = DateTime.Now;
oOrder.DocDate =Convert.ToDateTime(record.First().DocDate);
foreach (var recordItems in _salesOrders.SelectMany(e=>e.OrdersLines).Where(w=>w.DocReference ==record.First().DocReference))
{
oOrder.Lines.ItemCode = recordItems.ItemCode;
oOrder.Lines.ItemDescription = recordItems.Description;
oOrder.Lines.Quantity = Convert.ToDouble(recordItems.Qty);
oOrder.Lines.Price = Convert.ToDouble(recordItems.Price);
oOrder.Lines.Add();
log.Debug(string.Format("Order Line added to sap Item Code={0}, Description={1},Qty={2}", recordItems.ItemCode, recordItems.Description, recordItems.Qty));
}
int lRetCode = oOrder.Add(); // Try to add the orer to the database
}
if(lRetCode == 0)
{
string body = "Purchase Order Imported into SAP";
}
if (lRetCode != 0)
{
int temp_int = lErrCode;
string temp_string = sErrMsg;
company.GetLastError(out temp_int, out temp_string);
if (lErrCode != -4006) // Incase adding an order failed
{
log.Error(string.Format("Error adding an order into sap ErrorCode {0},{1}", temp_int, temp_string));
}
}
}
The problem you will see i have is how do I first split the csv into the two lists and second how do i access the header rows correctly in the strongly type object as you see I am using first which will not work correctly.
With FileHelpers it is important to avoid using the mapping class for anything other than describing the underlying file structure. Here I suspect you are trying to map directly to a class which is too complex.
A FileHelpers class is just a way of defining the specification of a flat file using C# syntax.
As such, the FileHelpers classes are an unusual type of C# class and you should not try to use accepted OOP principles. FileHelpers should not have properties or methods beyond the ones used by the FileHelpers library.
Think of the FileHelpers class as the 'specification' of your CSV format only. That should be its only role. (This is good practice from a maintenance perspective anyway - if the underlying CSV structure were to change, it is easier to adapt your code).
Then if you need the records in a more 'normal' object, then map the results to something better, that is, a class that encapsulates all the functionality of the Order object rather than the CSVOrder.
So, one way of handling this type of file is to parse the file twice. In the first pass you extract the header records. Something like this:
var engine1 = new FileHelperEngine<CSVHeaders>();
var headers = engine1.ReadFile(csvFileName);
In the second pass you extract the details;
var engine2 = new FileHelperEngine<CSVDetails>();
var details = engine2.ReadFile(csvFileName);
Then you combine this information into a new dedicated class, maybe with some LINQ similar to this
var niceOrders =
headers
.DistinctBy(h => h.OrderNumber)
.SelectMany(d => details.Where(d => d.OrderNumber = y))
.Select(x =>
new NiceOrder() {
OrderNumber = x.OrderNumber,
Customer = x.Customer,
ItemCode = x.ItemCode
// etc.
});

Linq: Transforming specified column's datatypes and values while preserving unspecified columns

I have a list of Order objects. Order has the properties: int Id, decimal Price, string OrderNumber, string ShipperState, DateTime TimeStamp;
I know which columns I want to transform (Price, TimeStamp) and I want to keep the other columns without needing to specify them.
This example is transforming specified columns but I still need to include the non-transformed columns.
var myList = model.Orders.Select(x => new
{
x.Id,
x.OrderNumber,
// decimal to string
Price = x.Price.ToString("C", new CultureInfo("en-US")),
x.ShipperState,
// DateTime to string
TimeStamp = x.TimeStamp.ToString("MM/dd/yyyy H:mm")
}
If I were to add a column string ShipperCity to the Order class, I would like myList to also have that property without having to go back and update the projection.
An ideal answer would not rely on external libraries, reflection and only be a line or two.
If you do not want to modify the model class as #David suggested you can write extension methods for it like this:
public static class OrderExtensions
{
public static string GetFormattedPrice(this Order order)
=> order.Price.ToString("C", new CultureInfo("en-US"));
public static string GetFormattedTimestamp(this Order order)
=> order.Timestamp.ToString("MM/dd/yyyy H:mm");
}
UPDATE #1
The effect of this alternative is that whereever you wanted to use the transformed order.Price and order.Timestamp there you have to use order.GetFormattedPrice() and order.GetFormattedTimestamp() respectively.
In the question it was not specified that where the data come from and what type of application the data is used in.
For example methods cannot be used in XAML binding and everywhere else where a property is required.
Please note:
In C# (almost) everything is strongly typed hence once the class and the properties in it are defined you cannot set one of its property value to a different type of data and also you cannot change the type of the property. So by default you cannot avoid projection when you need some transformation. If you need all the properties - either the original value or the transformed value - you have to list all of them in the projection.
almost everything except dynamic
You can actually transform the type and the value of a property but only if it is defined as dynamic. For example this works below:
public class Order
{
public int Id { get; set; }
public string OrderNumber { get; set; }
// Original: decimal; Converted: string;
public dynamic Price { get; set; }
public string ShipperState { get; set; }
// Original: DateTime; Converted: string;
public dynamic Timestamp { get; set; }
}
public static class OrderExtensions
{
public static void Transform(this Order order)
{
if (order.Price.GetType() == typeof(decimal))
order.Price = order.Price.ToString("C", new CultureInfo("en-US"));
if (order.Timestamp.GetType() == typeof(DateTime))
order.Timestamp = order.Timestamp.ToString("MM/dd/yyyy H:mm");
}
}
class Program
{
static void Main(string[] args)
{
var originalList = new List<Order>()
{
new Order() { Id = 1, OrderNumber = "1", Price = 100m, Timestamp = DateTime.Now },
new Order() { Id = 2, OrderNumber = "2", Price = 200m, Timestamp = DateTime.Now },
new Order() { Id = 3, OrderNumber = "3", Price = 300m, Timestamp = DateTime.Now }
};
originalList.ForEach(order => order.Transform());
}
}
Although this example works there are some things to know:
dynamic type
This example looks like a hack, maybe it can be considered as a hack. :)
In this example the original Order objects are changed not their projection/clone/etc.
dynamic properties are not allowed in Entity Framework models as you cannot specify the SQL column type for them even using the methods of DbModelBuilder. I did not try it in other use-cases but it seems to be a very restricted possibility.
For dynamic properties there is no IntelliSense, so after typing order.Price. no list would appear with any method or property.
You have to use these properties very carefully as there is no compile-time check. Any typo or other mistake will throw an exception only during run-time.
If this option somehow fits the needs it might be worth implementing the conversion of the string value back to the original type.
That's all the update I could add to my original answer. Hope this is an acceptable answer to your comment.

How to update object values (based on the curr-previous pattern)?

Assume you have a CSV file with the following simplified structure
LINE1: ID,Description,Value
LINE2: 1,Product1,2
LINE3: ,,3
LINE4: ,,4
LINE5: 2,Product2,2
LINE6: ,,3
LINE7: ,,5
I am using FileHelpers to read the CSV and have hooked up one the interfaces that allows me me to access the current line, after it has been read. Refer to this SO question for more background.
The issues is that using that approach I will need to write many more if statements to check all the fields that need to be copied. (I have at least, 6 csv files at the moment with the same 'blank' format all having more than 20 fields that need to be copied ~ 120 if statements. urggh)
Now this is not a micro optimisation exercise since there will be more files that will have this 'incomplete' format.
How can I update the previous record in an elegant way such that I wont have to write if conditions and declarations for each field?
The current solution is to annotate the required fields using a custom attribute called CopyMe and then use reflection to copy.
[DelimitedRecord(",") ]
[IgnoreFirst(1)]
public class Product
{
[CopyMe()] public int ID { get; set; }
[CopyMe()] public string Description { get; set; }
[FieldConverter(ConverterKind.Decimal)]
public decimal Val{ get; set; }
}
with the AfterRead method looking like so...
public void AfterRead(AfterReadEventArgs<RawVolsDataPoints> e)
{
var record = (Product)e.Record;
if (PreviousRecord == null)
{
PreviousRecord = new Product();
PreviousRecord = record;
}
if (String.IsNullOrEmpty(record.ID)) // null value indicates a new row
{
var ttype = typeof(Product);
var fields = ttype.GetFields();
var fieldsToCopy = fields.Where(field =>
field.GetCustomAttributes(typeof(CopyMeAttribute), true).Any());
foreach (var item in fieldsToCopy)
{
var prevvalue = item.GetValue(PreviousRecord);
item.SetValue(record, prevvalue);
}
}
}

WPF Vertical representation of a DataRow

I'm not sure where to look for this one... I've got a viewmodel that has an underlying DataRow providing part of the model. I want to display this information as a single record, in a vertical layout. I planned to use the DataGrid because I want the user to be able to add/delete/rename rows right across the DataTable despite only looking at one record. I'm not quite sure how to achieve this though. Example of what I'm expecting is below:
Source Data Table
ID, Name, Value
1, One, 1
2, Two, 2
Expected in my UI would be a table looking like the following
ID | 1
Name | One
Value | 1
You can expose the DataRow as a list of fields :
public class DataRowField
{
public int Index { get; set; }
public string Name { get; set; }
public object Value { get; set; }
}
public IEnumerable<DataRowField> Fields
{
get
{
return _dataRow.Table.Columns.Cast<DataColumn>()
.Select((column, i) => new DataRowField
{
Index = i,
Name = column.ColumnName,
Value = _dataRow[column]
});
}
}
Then you just need to bind your DataGrid to the Fields property

datasource to dsiplay in grid; list<ListofCarGroups> where ListofCarGroups is List<ListOfCars>

I understand that i cannot do something like List<List<string,string>>. So i have to put each object in a class, and then have a list of classes.
Below is the problem as I understand it.
Class FailedField has properties FieldName, Error
sample data: "year", "must be between 1900-2100"
Class FailedFields is a list of FailedField; Meaning a list of FailedField per record
sample data: { ("year", "must be between 1900-2100"
),("percentage", "must be between 0-100").... }
Class FailedFieldsList is a list of FailedFields for multiple records
In case you are wondering why I am trying to implement this data structure, i am collecting validation errors for each failed attempt to insert a record in LINQ-to-SQL.
My code so far:
In the partial class representing one SQL table:
public partial class FailedFields
{
public List<FailedField> ListOfFailedFieldsInOneRecord = new List<FailedField>();
public void addFailedField(FailedField f)
{
ListOfFailedFieldsInOneRecord.Add(f);
}
public int getFailedFieldsCount()
{
return ListOfFailedFieldsInOneRecord.Count();
}
}
public partial class FailedField
{
public string fieldName;
public string message;
public FailedField(string vField, string vMessage)
{
this.fieldName = vField;
this.message = vMessage;
}
public override string ToString()
{
return fieldName + ", " + message;
}
}
In the MyDataContext
public partial class MyClassesDataContext
{
public partial class FailedFieldsList
{
private static List<Slab.FailedFields> ErrorList = new List<Slab.FailedFields>();
public void AddErrorList(Slab.FailedFields errs)
{
ErrorList.Add(errs);
}
public List<Slab.FailedFields> GetErrorList()
{
return ErrorList;
}
}
}
And in Codebehind
MyClassesDataContext.FailedFieldsList FailedRecords = new MyClassesDataContext.FailedFieldsList();
ErrorBoxGridView.DataSource = FailedRecords.GetErrorList();
ErrorBoxGridView.DataBind();
Sample output
|------------------------------------------------------------------------------|
| Record # | Field_1 | Error_1 | Field_2 | Error_2 | ... | Field_10 | Error_10 |
|------------------------------------------------------------------------------|
| Data | Data | Data | Data | Data | ... | Data | Data |
|------------------------------------------------------------------------------|
From Visual Studio debugger, I know I am filling the objects upto the FailedFieldsList class. I am not sure where i am going wrong. Either the logic of the data structure is wrong, or i am not accessing the object correctly. Or maybe List is not the best tool in this case?
If there is something you think would help me understand where i am going wrong, please suggest any reference material you have.
I am stuck. Any help appreciated.
It sounds like you're trying to bind a list of list of FailedField records to a grid.
The problem is that the Grid will display what's in your (first) list, in this case each record in your list is itself a list, so what do you actually want the grid to show?
You either need to flatten your list of lists down to one list (with something to identify the groups if you like), or you could use a master/detail type of view if there's info about each list of string pairs you want to display, or you could use a tree to allow drilling down to the level you want.
Think about what you actually want to display and work backwards from there.

Categories

Resources