Skip to content

Commit

Permalink
Move project format to VS2017, pre-work for supporting netstandard 2.0
Browse files Browse the repository at this point in the history
Updated test framework to NUnit 3
  • Loading branch information
phatcher committed Jan 24, 2018
1 parent 507a625 commit 831feca
Show file tree
Hide file tree
Showing 26 changed files with 853 additions and 1,206 deletions.
13 changes: 10 additions & 3 deletions .gitignore
Expand Up @@ -9,6 +9,7 @@


# Build results
.vs/
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
Expand All @@ -17,7 +18,7 @@ build/
bld/
[Bb]in/
[Oo]bj/

.sonarqube/

# MSTest test Results
[Tt]est[Rr]esult*/
Expand Down Expand Up @@ -69,6 +70,7 @@ _Chutzpah*
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
Expand Down Expand Up @@ -144,6 +146,12 @@ publish/
*.[Pp]ublish.xml
*.azurePubxml

# Build tools/artifacts
.fake/
.paket/paket.exe
paket.restore.targets
packages/
paket-files/

# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
Expand Down Expand Up @@ -178,7 +186,6 @@ ClientBin/
*.pfx
*.publishsettings
node_modules/
tools/


# RIA/Silverlight projects
Expand Down Expand Up @@ -223,4 +230,4 @@ Desktop.ini


# Recycle Bin used on file shares
$RECYCLE.BIN/
$RECYCLE.BIN/
Binary file added .paket/paket.bootstrapper.exe
Binary file not shown.
37 changes: 37 additions & 0 deletions .paket/paket.targets
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<!-- Enable the restore command to run before builds -->
<RestorePackages Condition=" '$(RestorePackages)' == '' ">true</RestorePackages>
<!-- Download Paket.exe if it does not already exist -->
<DownloadPaket Condition=" '$(DownloadPaket)' == '' ">true</DownloadPaket>
<PaketToolsPath>$(MSBuildThisFileDirectory)</PaketToolsPath>
<PaketRootPath>$(MSBuildThisFileDirectory)..\</PaketRootPath>
</PropertyGroup>
<PropertyGroup>
<!-- Paket command -->
<PaketExePath Condition=" '$(PaketExePath)' == '' ">$(PaketToolsPath)paket.exe</PaketExePath>
<PaketBootStrapperExePath Condition=" '$(PaketBootStrapperExePath)' == '' ">$(PaketToolsPath)paket.bootstrapper.exe</PaketBootStrapperExePath>
<PaketCommand Condition=" '$(OS)' == 'Windows_NT'">"$(PaketExePath)"</PaketCommand>
<PaketCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(PaketExePath)</PaketCommand>
<PaketBootStrapperCommand Condition=" '$(OS)' == 'Windows_NT'">"$(PaketBootStrapperExePath)"</PaketBootStrapperCommand>
<PaketBootStrapperCommand Condition=" '$(OS)' != 'Windows_NT' ">mono --runtime=v4.0.30319 $(PaketBootStrapperExePath)</PaketBootStrapperCommand>
<!-- Commands -->
<PaketReferences>$(MSBuildProjectDirectory)\paket.references</PaketReferences>
<RestoreCommand>$(PaketCommand) restore --references-files $(PaketReferences)</RestoreCommand>
<DownloadPaketCommand>$(PaketBootStrapperCommand)</DownloadPaketCommand>
<!-- We need to ensure packages are restored prior to assembly resolve -->
<BuildDependsOn Condition="$(RestorePackages) == 'true'">RestorePackages; $(BuildDependsOn);</BuildDependsOn>
</PropertyGroup>
<Target Name="CheckPrerequisites">
<!-- Raise an error if we're unable to locate paket.exe -->
<Error Condition="'$(DownloadPaket)' != 'true' AND !Exists('$(PaketExePath)')" Text="Unable to locate '$(PaketExePath)'" />
<MsBuild Targets="DownloadPaket" Projects="$(MSBuildThisFileFullPath)" Properties="Configuration=NOT_IMPORTANT;DownloadPaket=$(DownloadPaket)" />
</Target>
<Target Name="DownloadPaket">
<Exec Command="$(DownloadPaketCommand)" Condition=" '$(DownloadPaket)' == 'true' AND !Exists('$(PaketExePath)')" />
</Target>
<Target Name="RestorePackages" DependsOnTargets="CheckPrerequisites">
<Exec Command="$(RestoreCommand)" WorkingDirectory="$(PaketRootPath)" Condition="Exists('$(PaketReferences)')" />
</Target>
</Project>
6 changes: 0 additions & 6 deletions CsvReader.sln
Expand Up @@ -22,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
RELEASE_NOTES.md = RELEASE_NOTES.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lumenworks.Framework.IO.Pcl", "code\LumenWorks.Framework.IO\Lumenworks.Framework.IO.Pcl.csproj", "{E564BC5A-8C07-43B9-BBD5-18CD3B4EDCF1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -50,10 +48,6 @@ Global
{800B3F74-702F-48C8-B3E9-AA658E3D5A05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{800B3F74-702F-48C8-B3E9-AA658E3D5A05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{800B3F74-702F-48C8-B3E9-AA658E3D5A05}.Release|Any CPU.Build.0 = Release|Any CPU
{E564BC5A-8C07-43B9-BBD5-18CD3B4EDCF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E564BC5A-8C07-43B9-BBD5-18CD3B4EDCF1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E564BC5A-8C07-43B9-BBD5-18CD3B4EDCF1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E564BC5A-8C07-43B9-BBD5-18CD3B4EDCF1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
194 changes: 97 additions & 97 deletions README.md
Expand Up @@ -17,7 +17,7 @@ You can see the version history [here](RELEASE_NOTES.md).
## Build the project
* Windows: Run *build.cmd*

I have my tools in C:\Tools so I use *build.cmd Default tools=C:\Tools encoding=UTF-8*
The tooling should be automatically installed by paket/Fake. The default build will compile and test the project, and also produce a nuget package.

## Library License

Expand Down Expand Up @@ -50,43 +50,43 @@ Having said that, there are some extensions built into this version of the libra
### Columns
One addition is the addition of a Column list which holds the names and types of the data in the CSV file. If there are no headers present, we default the column names to Column1, Column2 etc; this can be overridden by setting the DefaultColumnHeader property e.g.
```csharp
using System.IO;
using LumenWorks.Framework.IO.Csv;
using System.IO;
using LumenWorks.Framework.IO.Csv;

void ReadCsv()
void ReadCsv()
{
// open the file "data.csv" which is a CSV file with headers
using (var csv = new CachedCsvReader(new StreamReader("data.csv"), false))
{
// open the file "data.csv" which is a CSV file with headers
using (var csv = new CachedCsvReader(new StreamReader("data.csv"), false))
{
csv.DefaultColumnHeader = "Fred"
csv.DefaultColumnHeader = "Fred"

// Field headers will now be Fred1, Fred2, etc
myDataGrid.DataSource = csv;
}
// Field headers will now be Fred1, Fred2, etc
myDataGrid.DataSource = csv;
}
}
```

You can specify the columns yourself if there are none, and also specify the expected type; this is especially important when using against SqlBulkCopy which we will come back to later.
```csharp
using System.IO;
using LumenWorks.Framework.IO.Csv;
using System.IO;
using LumenWorks.Framework.IO.Csv;

void ReadCsv()
void ReadCsv()
{
// open the file "data.csv" which is a CSV file with headers
using (var csv = new CachedCsvReader(new StreamReader("data.csv"), false))
{
// open the file "data.csv" which is a CSV file with headers
using (var csv = new CachedCsvReader(new StreamReader("data.csv"), false))
{
csv.Columns.Add(new Column { Name = "PriceDate", Type = typeof(DateTime) });
csv.Columns.Add(new Column { Name = "OpenPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "HighPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "LowPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "ClosePrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "Volume", Type = typeof(int) });

// Field headers will now be picked from the Columns collection
myDataGrid.DataSource = csv;
}
csv.Columns.Add(new Column { Name = "PriceDate", Type = typeof(DateTime) });
csv.Columns.Add(new Column { Name = "OpenPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "HighPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "LowPrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "ClosePrice", Type = typeof(decimal) });
csv.Columns.Add(new Column { Name = "Volume", Type = typeof(int) });

// Field headers will now be picked from the Columns collection
myDataGrid.DataSource = csv;
}
}
```

### SQL Bulk Copy
Expand All @@ -98,89 +98,89 @@ A couple of issues arise when using SBC

Below is a example using the Columns collection to set up the correct metadata for SBC
```csharp
public void Import(string fileName, string connectionString)
{
using (var reader = new CsvReader(new StreamReader(fileName), false))
{
reader.Columns = new List<LumenWorks.Framework.IO.Csv.Column>
{
new LumenWorks.Framework.IO.Csv.Column { Name = "PriceDate", Type = typeof(DateTime) },
new LumenWorks.Framework.IO.Csv.Column { Name = "OpenPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "HighPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "LowPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "ClosePrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "Volume", Type = typeof(int) },
};

// Now use SQL Bulk Copy to move the data
using (var sbc = new SqlBulkCopy(connectionString))
{
sbc.DestinationTableName = "dbo.DailyPrice";
sbc.BatchSize = 1000;

sbc.AddColumnMapping("PriceDate", "PriceDate");
sbc.AddColumnMapping("OpenPrice", "OpenPrice");
sbc.AddColumnMapping("HighPrice", "HighPrice");
sbc.AddColumnMapping("LowPrice", "LowPrice");
sbc.AddColumnMapping("ClosePrice", "ClosePrice");
sbc.AddColumnMapping("Volume", "Volume");

sbc.WriteToServer(reader);
}
}
}
public void Import(string fileName, string connectionString)
{
using (var reader = new CsvReader(new StreamReader(fileName), false))
{
reader.Columns = new List<LumenWorks.Framework.IO.Csv.Column>
{
new LumenWorks.Framework.IO.Csv.Column { Name = "PriceDate", Type = typeof(DateTime) },
new LumenWorks.Framework.IO.Csv.Column { Name = "OpenPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "HighPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "LowPrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "ClosePrice", Type = typeof(decimal) },
new LumenWorks.Framework.IO.Csv.Column { Name = "Volume", Type = typeof(int) },
};

// Now use SQL Bulk Copy to move the data
using (var sbc = new SqlBulkCopy(connectionString))
{
sbc.DestinationTableName = "dbo.DailyPrice";
sbc.BatchSize = 1000;

sbc.AddColumnMapping("PriceDate", "PriceDate");
sbc.AddColumnMapping("OpenPrice", "OpenPrice");
sbc.AddColumnMapping("HighPrice", "HighPrice");
sbc.AddColumnMapping("LowPrice", "LowPrice");
sbc.AddColumnMapping("ClosePrice", "ClosePrice");
sbc.AddColumnMapping("Volume", "Volume");

sbc.WriteToServer(reader);
}
}
}
```
The method AddColumnMapping is an extension I wrote to simplify adding mappings to SBC
```csharp
public static class SqlBulkCopyExtensions
public static class SqlBulkCopyExtensions
{
public static SqlBulkCopyColumnMapping AddColumnMapping(this SqlBulkCopy sbc, int sourceColumnOrdinal, int targetColumnOrdinal)
{
public static SqlBulkCopyColumnMapping AddColumnMapping(this SqlBulkCopy sbc, int sourceColumnOrdinal, int targetColumnOrdinal)
{
var map = new SqlBulkCopyColumnMapping(sourceColumnOrdinal, targetColumnOrdinal);
sbc.ColumnMappings.Add(map);
var map = new SqlBulkCopyColumnMapping(sourceColumnOrdinal, targetColumnOrdinal);
sbc.ColumnMappings.Add(map);

return map;
}
return map;
}

public static SqlBulkCopyColumnMapping AddColumnMapping(this SqlBulkCopy sbc, string sourceColumn, string targetColumn)
{
var map = new SqlBulkCopyColumnMapping(sourceColumn, targetColumn);
sbc.ColumnMappings.Add(map);
public static SqlBulkCopyColumnMapping AddColumnMapping(this SqlBulkCopy sbc, string sourceColumn, string targetColumn)
{
var map = new SqlBulkCopyColumnMapping(sourceColumn, targetColumn);
sbc.ColumnMappings.Add(map);

return map;
}
return map;
}
}
```
One other issue recently arose where we wanted to use SBC but some of the data was not in the file itself, but metadata that needed to be included on every row. The solution was to amend the CSV reader and Columns collection to allow default values to be provided that are not in the data.

The additional columns should be added at the end of the Columns collection to avoid interfering with the parsing, see the amended example below...
```csharp
public void Import(string fileName, string connectionString)
{
using (var reader = new CsvReader(new StreamReader(fileName), false))
{
reader.Columns = new List<LumenWorks.Framework.IO.Csv.Column>
{
...
new LumenWorks.Framework.IO.Csv.Column { Name = "Volume", Type = typeof(int) },
// NB Fake column so bulk import works
new LumenWorks.Framework.IO.Csv.Column { Name = "Ticker", Type = typeof(string) },
};

// Fix up the column defaults with the values we need
reader.UseColumnDefaults = true;
reader.Columns[reader.GetFieldIndex("Ticker")] = Path.GetFileNameWithoutExtension(fileName);

// Now use SQL Bulk Copy to move the data
using (var sbc = new SqlBulkCopy(connectionString))
{
...
sbc.AddColumnMapping("Ticker", "Ticker");

sbc.WriteToServer(reader);
}
}
}
public void Import(string fileName, string connectionString)
{
using (var reader = new CsvReader(new StreamReader(fileName), false))
{
reader.Columns = new List<LumenWorks.Framework.IO.Csv.Column>
{
...
new LumenWorks.Framework.IO.Csv.Column { Name = "Volume", Type = typeof(int) },
// NB Fake column so bulk import works
new LumenWorks.Framework.IO.Csv.Column { Name = "Ticker", Type = typeof(string) },
};

// Fix up the column defaults with the values we need
reader.UseColumnDefaults = true;
reader.Columns[reader.GetFieldIndex("Ticker")] = Path.GetFileNameWithoutExtension(fileName);

// Now use SQL Bulk Copy to move the data
using (var sbc = new SqlBulkCopy(connectionString))
{
...
sbc.AddColumnMapping("Ticker", "Ticker");

sbc.WriteToServer(reader);
}
}
}
```
To give an idea of performance, this took a naive sample app using an ORM from 2m 27s to 1.37s using SBC and the full import took just over 11m to import 9.8m records.

Expand Down
22 changes: 16 additions & 6 deletions build.cmd
@@ -1,13 +1,23 @@
@echo off
SETLOCAL

cls

if not exist packages\FAKE\tools\Fake.exe (
nuget\nuget.exe install FAKE -OutputDirectory packages -ExcludeVersion
.paket\paket.bootstrapper.exe
if errorlevel 1 (
exit /b %errorlevel%
)

SET TARGET="Default"
.paket\paket.exe restore
if errorlevel 1 (
exit /b %errorlevel%
)

if NOT [%1]==[] (set TARGET="%1")
SET FAKE_PATH=packages\build\FAKE\tools\Fake.exe
SET Platform=

"packages\FAKE\tools\FAKE.exe" "build.fsx" "target=%TARGET%" %*
pause
IF [%1]==[] (
"%FAKE_PATH%" "build.fsx" "Default"
) ELSE (
"%FAKE_PATH%" "build.fsx" %*
)

0 comments on commit 831feca

Please sign in to comment.