├── .gitignore
├── .nuget
├── NuGet.Config
├── NuGet.exe
└── NuGet.targets
├── .vscode
├── launch.json
└── tasks.json
├── Gridsum.DataflowEx.Demo
├── AggregatorFlow.cs
├── App.config
├── CircularFlow.cs
├── ComplexIntFlow.cs
├── ForStackoverflow
│ └── CompletionDemo1.cs
├── Gridsum.DataflowEx.Demo.csproj
├── LineAggregatorFlow.cs
├── LocalDBUtil.cs
├── MyLogger.cs
├── NLog.config
├── NLog.xsd
├── Order.cs
├── PeopleFlow.cs
├── Program.cs
└── SlowFlow.cs
├── Gridsum.DataflowEx.Test
├── App.config
├── BlockExtensionTest.cs
├── DatabaseTests
│ ├── BulkDataReaderTest.cs
│ ├── BulkInserterTest.cs
│ ├── DbColumnPathTest.cs
│ ├── ExternalMappingTest.cs
│ ├── GSProduct.cs
│ ├── NestClassTest.cs
│ ├── ReferenceLoopDepthTest.cs
│ └── TestBootstrapper.cs
├── DataflowFaultTest.cs
├── DataflowTest.cs
├── Demo
│ ├── Order.cs
│ ├── PeopleFlow.cs
│ └── SlowFlow.cs
├── ETL
│ ├── TestDataJoiner.cs
│ └── TestDataJoiner2.cs
├── Gridsum.DataflowEx.Test.csproj
├── TestAutoComplete.cs
├── TestBlockBehaviors.cs
├── TestDataBroadcaster.cs
├── TestFlowBlocking.cs
├── TestImmutable.cs
├── TestLogReader.cs
├── TestStatisticsRecorder.cs
├── TestStringCondition.cs
└── TestUtils.cs
├── Gridsum.DataflowEx.TestHelper
├── App.config
├── Gridsum.DataflowEx.TestHelper.csproj
└── Program.cs
├── Gridsum.DataflowEx.sln
├── Gridsum.DataflowEx
├── AutoCompletion
│ ├── AutoCompleteWrapper.cs
│ ├── ControlItem.cs
│ ├── ControlType.cs
│ ├── HeartbeatNode.cs
│ ├── IRingNode.cs
│ ├── ITracableItem.cs
│ ├── RingMonitor.cs
│ └── TracableItemBase.cs
├── Blocks
│ └── TransformAndLinkPropagator.cs
├── DataBroadcaster.cs
├── DataDispatcher.cs
├── Databases
│ ├── BulkDataReader.cs
│ ├── DBColumnMapping.cs
│ ├── DBColumnPath.cs
│ ├── DbBulkInserter.cs
│ ├── DbBulkInserterBase.cs
│ ├── EagerDbBulkInserter.cs
│ ├── InvalidDBColumnMappingException.cs
│ ├── MultiDbBulkInserter.cs
│ ├── NoNullCheckAttribute.cs
│ ├── PostBulkInsertDelegate.cs
│ ├── TargetTable.cs
│ └── TypeAccessor.cs
├── Dataflow.cs
├── DataflowBlockExtensions.cs
├── DataflowMerger.cs
├── DataflowOptions.cs
├── DataflowUtils.cs
├── ETL
│ ├── ByteArrayEqualityComparer.cs
│ ├── DbDataJoiner.cs
│ ├── IDimRow.cs
│ ├── PartialDimRow.cs
│ └── RowCache.cs
├── Exceptions
│ ├── NoChildRegisteredException.cs
│ ├── PostToBlockFailedException.cs
│ ├── PropagatedException.cs
│ └── TaskEx.cs
├── Gridsum.DataflowEx.csproj
├── IDataflow.cs
├── IDataflowDependency.cs
├── ImmutableUtils.cs
├── IntHolder.cs
├── LogHelper.cs
├── LogReader.cs
├── PatternMatch
│ ├── IMatchCondition.cs
│ ├── IMatchable.cs
│ ├── MatchConditionBase.cs
│ ├── MatchNoneCondition.cs
│ ├── MatchType.cs
│ ├── MultiMatcher.cs
│ ├── StringMatchCondition.cs
│ └── UrlStringMatchCondition.cs
├── PropagatorDataflow.cs
├── StatisticsRecorder.cs
├── TargetDataflow.cs
└── Utils.cs
├── License.txt
├── README.md
├── global.json
└── images
├── MyLoggerDemo.png
├── dbbulkinserter_screenshot1.png
├── dbbulkinserter_screenshot2.png
├── lookupDemo1.png
└── lookupDemo2.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # Nuget packages
5 | *.nupkg
6 |
7 | # User-specific files
8 | *.suo
9 | *.user
10 | *.sln.docstates
11 |
12 | # Build results
13 | [Dd]ebug/
14 | [Dd]ebugPublic/
15 | [Rr]elease/
16 | x64/
17 | build/
18 | bld/
19 | [Bb]in/
20 | [Oo]bj/
21 |
22 | # MSTest test Results
23 | [Tt]est[Rr]esult*/
24 | [Bb]uild[Ll]og.*
25 |
26 | #NUNIT
27 | *.VisualState.xml
28 | TestResult.xml
29 |
30 | # Build Results of an ATL Project
31 | [Dd]ebugPS/
32 | [Rr]eleasePS/
33 | dlldata.c
34 |
35 | *_i.c
36 | *_p.c
37 | *_i.h
38 | *.ilk
39 | *.meta
40 | *.obj
41 | *.pch
42 | *.pdb
43 | *.pgc
44 | *.pgd
45 | *.rsp
46 | *.sbr
47 | *.tlb
48 | *.tli
49 | *.tlh
50 | *.tmp
51 | *.tmp_proj
52 | *.log
53 | *.vspscc
54 | *.vssscc
55 | .builds
56 | *.pidb
57 | *.svclog
58 | *.scc
59 |
60 | # Chutzpah Test files
61 | _Chutzpah*
62 |
63 | # Visual C++ cache files
64 | ipch/
65 | *.aps
66 | *.ncb
67 | *.opensdf
68 | *.sdf
69 | *.cachefile
70 |
71 | # Visual Studio profiler
72 | *.psess
73 | *.vsp
74 | *.vspx
75 |
76 | # TFS 2012 Local Workspace
77 | $tf/
78 |
79 | # Guidance Automation Toolkit
80 | *.gpState
81 |
82 | # ReSharper is a .NET coding add-in
83 | _ReSharper*/
84 | *.[Rr]e[Ss]harper
85 | *.DotSettings.user
86 |
87 | # JustCode is a .NET coding addin-in
88 | .JustCode
89 |
90 | # TeamCity is a build add-in
91 | _TeamCity*
92 |
93 | # DotCover is a Code Coverage Tool
94 | *.dotCover
95 |
96 | # NCrunch
97 | *.ncrunch*
98 | _NCrunch_*
99 | .*crunch*.local.xml
100 |
101 | # MightyMoose
102 | *.mm.*
103 | AutoTest.Net/
104 |
105 | # Web workbench (sass)
106 | .sass-cache/
107 |
108 | # Installshield output folder
109 | [Ee]xpress/
110 |
111 | # DocProject is a documentation generator add-in
112 | DocProject/buildhelp/
113 | DocProject/Help/*.HxT
114 | DocProject/Help/*.HxC
115 | DocProject/Help/*.hhc
116 | DocProject/Help/*.hhk
117 | DocProject/Help/*.hhp
118 | DocProject/Help/Html2
119 | DocProject/Help/html
120 |
121 | # Click-Once directory
122 | publish/
123 |
124 | # Publish Web Output
125 | *.[Pp]ublish.xml
126 | *.azurePubxml
127 |
128 | # NuGet Packages Directory
129 | packages/
130 | ## TODO: If the tool you use requires repositories.config uncomment the next line
131 | #!packages/repositories.config
132 |
133 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
134 | # This line needs to be after the ignore of the build folder (and the packages folder if the line above has been uncommented)
135 | !packages/build/
136 |
137 | # Windows Azure Build Output
138 | csx/
139 | *.build.csdef
140 |
141 | # Windows Store app package directory
142 | AppPackages/
143 |
144 | # Others
145 | sql/
146 | *.Cache
147 | ClientBin/
148 | [Ss]tyle[Cc]op.*
149 | ~$*
150 | *~
151 | *.dbmdl
152 | *.dbproj.schemaview
153 | *.pfx
154 | *.publishsettings
155 | node_modules/
156 |
157 | # RIA/Silverlight projects
158 | Generated_Code/
159 |
160 | # Backup & report files from converting an old project file to a newer
161 | # Visual Studio version. Backup files are not needed, because we have git ;-)
162 | _UpgradeReport_Files/
163 | Backup*/
164 | UpgradeLog*.XML
165 | UpgradeLog*.htm
166 |
167 | # SQL Server files
168 | *.mdf
169 | *.ldf
170 |
171 | # Business Intelligence projects
172 | *.rdl.data
173 | *.bim.layout
174 | *.bim_*.settings
175 |
176 | # Microsoft Fakes
177 | FakesAssemblies/
178 | /.vs/Gridsum.DataflowEx/v15/sqlite3/storage.ide
179 | .vs/config/applicationhost.config
180 |
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.nuget/NuGet.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gridsum/DataflowEx/552b0b45eeb4a513c4079a10d0bc57df4950448f/.nuget/NuGet.exe
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to find out which attributes exist for C# debugging
3 | // Use hover for the description of the existing attributes
4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": ".NET Core Launch (console)",
9 | "type": "coreclr",
10 | "request": "launch",
11 | "preLaunchTask": "build",
12 | // If you have changed target frameworks, make sure to update the program path.
13 | "program": "${workspaceRoot}/Gridsum.DataflowEx.Test/bin/Debug/netcoreapp2.0/Gridsum.DataflowEx.Test.dll",
14 | "args": [],
15 | "cwd": "${workspaceRoot}/Gridsum.DataflowEx.Test",
16 | // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window
17 | "console": "internalConsole",
18 | "stopAtEntry": false,
19 | "internalConsoleOptions": "openOnSessionStart"
20 | },
21 | {
22 | "name": ".NET Core Attach",
23 | "type": "coreclr",
24 | "request": "attach",
25 | "processId": "${command:pickProcess}"
26 | }
27 | ]
28 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.1.0",
3 | "command": "dotnet",
4 | "isShellCommand": true,
5 | "args": [],
6 | "tasks": [
7 | {
8 | "taskName": "build",
9 | "args": [
10 | "${workspaceRoot}/Gridsum.DataflowEx.Test/Gridsum.DataflowEx.Test.csproj"
11 | ],
12 | "isBuildCommand": true,
13 | "problemMatcher": "$msCompile"
14 | }
15 | ]
16 | }
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/AggregatorFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Demo
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Threading.Tasks.Dataflow;
6 |
7 | using Gridsum.DataflowEx;
8 |
9 | public class AggregatorFlow : Dataflow
10 | {
11 | //Blocks
12 | private TransformBlock> _splitter;
13 | private ActionBlock> _aggregater;
14 |
15 | //Data
16 | private Dictionary _dict;
17 |
18 | public AggregatorFlow() : base(DataflowOptions.Default)
19 | {
20 | this._splitter = new TransformBlock>((Func>)this.Split);
21 | this._dict = new Dictionary();
22 | this._aggregater = new ActionBlock>((Action>)this.Aggregate);
23 |
24 | //Block linking
25 | this._splitter.LinkTo(this._aggregater, new DataflowLinkOptions() { PropagateCompletion = true });
26 |
27 | /* IMPORTANT */
28 | this.RegisterChild(this._splitter);
29 | this.RegisterChild(this._aggregater);
30 | }
31 |
32 | protected virtual void Aggregate(KeyValuePair pair)
33 | {
34 | int oldValue;
35 | this._dict[pair.Key] = this._dict.TryGetValue(pair.Key, out oldValue) ? oldValue + pair.Value : pair.Value;
36 | }
37 |
38 | protected virtual KeyValuePair Split(string input)
39 | {
40 | string[] splitted = input.Split('=');
41 | return new KeyValuePair(splitted[0], int.Parse(splitted[1]));
42 | }
43 |
44 | public override ITargetBlock InputBlock { get { return this._splitter; } }
45 |
46 | public IDictionary Result { get { return this._dict; } }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/CircularFlow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo
8 | {
9 | using System.Threading.Tasks.Dataflow;
10 |
11 | using Gridsum.DataflowEx.AutoCompletion;
12 |
13 | public class CircularFlow : Dataflow
14 | {
15 | private Dataflow _buffer;
16 |
17 | public CircularFlow(DataflowOptions dataflowOptions) : base(dataflowOptions)
18 | {
19 | //a no-op node to demonstrate the usage of preTask param in RegisterChildRing
20 | _buffer = new BufferBlock().ToDataflow(name: "NoOpBuffer");
21 |
22 | var heater = new TransformManyDataflow(async i =>
23 | {
24 | await Task.Delay(200);
25 | Console.WriteLine("Heated to {0}", i + 1);
26 | return new [] {i + 1};
27 | }, dataflowOptions);
28 | heater.Name = "Heater";
29 |
30 | var cooler = new TransformManyDataflow(async i =>
31 | {
32 | await Task.Delay(200);
33 | int cooled = i - 2;
34 | Console.WriteLine("Cooled to {0}", cooled);
35 |
36 | if (cooled < 0) //time to stop
37 | {
38 | return Enumerable.Empty();
39 | }
40 |
41 | return new [] {cooled};
42 | }, dataflowOptions);
43 | cooler.Name = "Cooler";
44 |
45 | var heartbeat = new HeartbeatNode(dataflowOptions) {Name = "HeartBeat"};
46 |
47 | _buffer.LinkTo(heater);
48 |
49 | //circular
50 | heater.LinkTo(cooler);
51 | cooler.LinkTo(heartbeat);
52 | heartbeat.LinkTo(heater);
53 |
54 | RegisterChildren(_buffer, heater, cooler, heartbeat);
55 |
56 | //ring registration
57 | RegisterChildRing(_buffer.CompletionTask, heater, cooler, heartbeat);
58 | }
59 |
60 | public override ITargetBlock InputBlock { get { return _buffer.InputBlock; } }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/ComplexIntFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Demo
2 | {
3 | using System;
4 | using System.Threading.Tasks;
5 | using System.Threading.Tasks.Dataflow;
6 | using Gridsum.DataflowEx;
7 |
8 | public class ComplexIntFlow : Dataflow
9 | {
10 | private ITargetBlock _headBlock;
11 | public ComplexIntFlow() : base(DataflowOptions.Default)
12 | {
13 | Dataflow node2 = DataflowUtils.FromDelegate(i => i);
14 | Dataflow node3 = DataflowUtils.FromDelegate(i => i * -1);
15 |
16 | Dataflow node1 = DataflowUtils.FromDelegate(
17 | i => {
18 | if (i % 2 == 0) { node2.Post(i); }
19 | else { node3.Post(i); }
20 | return 999;
21 | });
22 |
23 | Dataflow printer = DataflowUtils.FromDelegate(Console.WriteLine);
24 |
25 | node1.Name = "node1";
26 | node2.Name = "node2";
27 | node3.Name = "node3";
28 | printer.Name = "printer";
29 |
30 | this.RegisterChild(node1);
31 | this.RegisterChild(node2);
32 | this.RegisterChild(node3);
33 | this.RegisterChild(printer, t =>
34 | {
35 | if (t.Status == TaskStatus.RanToCompletion)
36 | Console.WriteLine("Printer done!");
37 | });
38 |
39 | node1.LinkTo(printer);
40 | node2.LinkTo(printer);
41 | node3.LinkTo(printer);
42 |
43 | //Completion propagation: node1 ---> node2
44 | node2.RegisterDependency(node1);
45 | //Completion propagation: node1 + node2 ---> node3
46 | node3.RegisterDependency(node1);
47 | node3.RegisterDependency(node2);
48 |
49 | this._headBlock = node1.InputBlock;
50 | }
51 |
52 | public override ITargetBlock InputBlock { get { return this._headBlock; } }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/ForStackoverflow/CompletionDemo1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo.ForStackoverflow
8 | {
9 | using System.Threading;
10 | using System.Threading.Tasks.Dataflow;
11 |
12 | ///
13 | /// For http://stackoverflow.com/questions/13510094/tpl-dataflow-guarantee-completion-only-when-all-source-data-blocks-completed
14 | ///
15 | public class CompletionDemo1
16 | {
17 | private Dataflow broadCaster;
18 | private TransformBlock transformBlock1;
19 | private TransformBlock transformBlock2;
20 | private Dataflow processor;
21 |
22 | public CompletionDemo1()
23 | {
24 | broadCaster = new BroadcastBlock(
25 | i =>
26 | {
27 | return i;
28 | }).ToDataflow();
29 |
30 | transformBlock1 = new TransformBlock(
31 | i =>
32 | {
33 | Console.WriteLine("1 input count: " + transformBlock1.InputCount);
34 | Thread.Sleep(50);
35 | return ("1_" + i);
36 | });
37 |
38 | transformBlock2 = new TransformBlock(
39 | i =>
40 | {
41 | Console.WriteLine("2 input count: " + transformBlock2.InputCount);
42 | Thread.Sleep(20);
43 | return ("2_" + i);
44 | });
45 |
46 | processor = new ActionBlock(
47 | i =>
48 | {
49 | Console.WriteLine(i);
50 | }).ToDataflow();
51 |
52 | /** rather than TPL linking
53 | broadCastBlock.LinkTo(transformBlock1, new DataflowLinkOptions { PropagateCompletion = true });
54 | broadCastBlock.LinkTo(transformBlock2, new DataflowLinkOptions { PropagateCompletion = true });
55 | transformBlock1.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
56 | transformBlock2.LinkTo(processorBlock, new DataflowLinkOptions { PropagateCompletion = true });
57 | **/
58 |
59 | //Use DataflowEx linking
60 | var transform1 = transformBlock1.ToDataflow();
61 | var transform2 = transformBlock2.ToDataflow();
62 |
63 | broadCaster.LinkTo(transform1);
64 | broadCaster.LinkTo(transform2);
65 | transform1.LinkTo(processor);
66 | transform2.LinkTo(processor);
67 | }
68 |
69 | public void Start()
70 | {
71 | const int numElements = 100;
72 |
73 | for (int i = 1; i <= numElements; i++)
74 | {
75 | broadCaster.SendAsync(i);
76 | }
77 |
78 | //mark completion
79 | broadCaster.Complete();
80 |
81 | processor.CompletionTask.Wait();
82 |
83 | Console.WriteLine("Finished");
84 | Console.ReadLine();
85 | }
86 |
87 | /*
88 | public static void Main()
89 | {
90 | var demo = new CompletionDemo1();
91 | demo.Start();
92 | }
93 | */
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/Gridsum.DataflowEx.Demo.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 | Exe
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/LineAggregatorFlow.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Demo
2 | {
3 | using System.Threading.Tasks.Dataflow;
4 | using Gridsum.DataflowEx;
5 |
6 | public class LineAggregatorFlow : Dataflow
7 | {
8 | private Dataflow _lineProcessor;
9 | private AggregatorFlow _aggregator;
10 | public LineAggregatorFlow() : base(DataflowOptions.Default)
11 | {
12 | this._lineProcessor = new TransformManyBlock(line => line.Split(' ')).ToDataflow();
13 | this._aggregator = new AggregatorFlow();
14 | this._lineProcessor.LinkTo(this._aggregator);
15 | this.RegisterChild(this._lineProcessor);
16 | this.RegisterChild(this._aggregator);
17 | }
18 |
19 | public override ITargetBlock InputBlock { get { return this._lineProcessor.InputBlock; } }
20 | public int this[string key] { get { return this._aggregator.Result[key]; } }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/LocalDBUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo
8 | {
9 | using System.Data.SqlClient;
10 | using System.IO;
11 | using System.Reflection;
12 |
13 | //Borrowed from https://social.msdn.microsoft.com/Forums/sqlserver/en-US/268c3411-102a-4272-b305-b14e29604313/localdb-create-connect-to-database-programmatically-?forum=sqlsetupandupgrade
14 | public static class LocalDB
15 | {
16 | public const string DB_DIRECTORY = "Data";
17 |
18 | [Obsolete]
19 | public static SqlConnection GetLocalDB(string dbName, bool deleteIfExists = false)
20 | {
21 | try
22 | {
23 | string outputFolder = Path.Combine(
24 | Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location),
25 | DB_DIRECTORY);
26 | string mdfFilename = dbName + ".mdf";
27 | string dbFileName = Path.Combine(outputFolder, mdfFilename);
28 | string logFileName = Path.Combine(outputFolder, String.Format("{0}_log.ldf", dbName));
29 | // Create Data Directory If It Doesn't Already Exist.
30 | if (!Directory.Exists(outputFolder))
31 | {
32 | Directory.CreateDirectory(outputFolder);
33 | }
34 |
35 | // If the file exists, and we want to delete old data, remove it here and create a new database.
36 | if (File.Exists(dbFileName) && deleteIfExists)
37 | {
38 | if (File.Exists(logFileName)) File.Delete(logFileName);
39 | File.Delete(dbFileName);
40 | CreateDatabase(dbName, dbFileName);
41 | }
42 | // If the database does not already exist, create it.
43 | else if (!File.Exists(dbFileName))
44 | {
45 | CreateDatabase(dbName, dbFileName);
46 | }
47 |
48 | // Open newly created, or old database.
49 | string connectionString =
50 | String.Format(
51 | @"Data Source=(LocalDB)\v11.0;AttachDBFileName={1};Initial Catalog={0};Integrated Security=True;",
52 | dbName,
53 | dbFileName);
54 | SqlConnection connection = new SqlConnection(connectionString);
55 | connection.Open();
56 | return connection;
57 | }
58 | catch
59 | {
60 | throw;
61 | }
62 | }
63 |
64 | private static bool CreateDatabase(string dbName, string dbFileName)
65 | {
66 | try
67 | {
68 | string connectionString =
69 | String.Format(@"Data Source=(LocalDB)\v11.0;Initial Catalog=master;Integrated Security=True");
70 | using (var connection = new SqlConnection(connectionString))
71 | {
72 | connection.Open();
73 | SqlCommand cmd = connection.CreateCommand();
74 |
75 |
76 | DetachDatabase(dbName);
77 |
78 | cmd.CommandText = String.Format(
79 | "CREATE DATABASE {0} ON (NAME = N'{0}', FILENAME = '{1}')",
80 | dbName,
81 | dbFileName);
82 | cmd.ExecuteNonQuery();
83 | }
84 |
85 | if (File.Exists(dbFileName)) return true;
86 | else return false;
87 | }
88 | catch
89 | {
90 | throw;
91 | }
92 | }
93 |
94 | private static bool DetachDatabase(string dbName)
95 | {
96 | try
97 | {
98 | string connectionString =
99 | String.Format(@"Data Source=(LocalDB)\v11.0;Initial Catalog=master;Integrated Security=True");
100 | using (var connection = new SqlConnection(connectionString))
101 | {
102 | connection.Open();
103 | SqlCommand cmd = connection.CreateCommand();
104 | cmd.CommandText = String.Format("exec sp_detach_db '{0}'", dbName);
105 | cmd.ExecuteNonQuery();
106 |
107 | return true;
108 | }
109 | }
110 | catch
111 | {
112 | return false;
113 | }
114 | }
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/MyLogger.cs:
--------------------------------------------------------------------------------
1 | using Common.Logging;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Gridsum.DataflowEx.Demo
9 | {
10 | using System.IO;
11 | using System.Threading.Tasks.Dataflow;
12 |
13 | public class MyLog
14 | {
15 | public LogLevel Level { get; set; }
16 | public string Message { get; set; }
17 | }
18 |
19 | ///
20 | /// Logger that accepts logs and dispatch them to appropriate dynamically created log writer
21 | ///
22 | public class MyLogger : DataDispatcher
23 | {
24 | public MyLogger() : base(log => log.Level)
25 | {
26 | }
27 |
28 | ///
29 | /// This function will only be called once for each distinct dispatchKey (the first time)
30 | ///
31 | protected override Dataflow CreateChildFlow(LogLevel dispatchKey)
32 | {
33 | //dynamically create a log writer by the dispatchKey (i.e. the log level)
34 | var writer = new LogWriter(string.Format(@".\MyLogger-{0}.log", dispatchKey));
35 |
36 | //no need to call RegisterChild(writer) here as DataDispatcher will call automatically
37 | return writer;
38 | }
39 | }
40 |
41 | ///
42 | /// Log writer node for a single destination file
43 | ///
44 | internal class LogWriter : Dataflow
45 | {
46 | private readonly ActionBlock m_writerBlock;
47 | private readonly StreamWriter m_writer;
48 |
49 | public LogWriter(string fileName) : base(DataflowOptions.Default)
50 | {
51 | this.m_writer = new StreamWriter(new FileStream(fileName, FileMode.Append));
52 |
53 | m_writerBlock = new ActionBlock(log => m_writer.WriteLine("[{0}] {1}", log.Level, log.Message));
54 |
55 | RegisterChild(m_writerBlock);
56 | }
57 |
58 | public override ITargetBlock InputBlock { get { return m_writerBlock; } }
59 |
60 | protected override void CleanUp(Exception e)
61 | {
62 | base.CleanUp(e);
63 | m_writer.Flush();
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/NLog.config:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/Order.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo
8 | {
9 | using System.Linq.Expressions;
10 |
11 | using Gridsum.DataflowEx.Databases;
12 | using Gridsum.DataflowEx.ETL;
13 |
14 | public class Order
15 | {
16 | [DBColumnMapping("OrderTarget", "Date", null)]
17 | public DateTime OrderDate { get; set; }
18 |
19 | [DBColumnMapping("OrderTarget", "Value", 0.0f)]
20 | public float? OrderValue { get; set; }
21 |
22 | [DBColumnPath(DBColumnPathOptions.DoNotGenerateNullCheck)]
23 | public Person Customer { get; set; }
24 | }
25 |
26 | public class OrderEx : Order
27 | {
28 | public OrderEx()
29 | {
30 | this.OrderDate = DateTime.Now;
31 | }
32 |
33 | // This is the field we want to populate
34 | [DBColumnMapping("LookupDemo", "ProductKey")]
35 | [DBColumnMapping("OrderTarget", "ProductKey")]
36 | public long? ProductKey { get; set; }
37 |
38 | public Product Product { get; set; }
39 | }
40 |
41 | public class Product
42 | {
43 | public Product(string category, string name)
44 | {
45 | this.Category = category;
46 | this.Name = name;
47 | }
48 |
49 | [DBColumnMapping("LookupDemo", "Category")]
50 | public string Category { get; private set; }
51 |
52 | [DBColumnMapping("LookupDemo", "ProductName")]
53 | public string Name { get; private set; }
54 |
55 | [DBColumnMapping("LookupDemo", "ProductFullName")]
56 | public string FullName { get { return string.Format("{0}-{1}", Category, Name); } }
57 | }
58 |
59 | public class ProductLookupFlow : DbDataJoiner
60 | {
61 | public ProductLookupFlow(TargetTable dimTableTarget, int batchSize = 8192, int cacheSize = 1048576)
62 | : base(
63 | //Tells DbDataJoiner the join condition is 'OrderEx.Product.FullName = TABLE.ProductFullName'
64 | o => o.Product.FullName,
65 | dimTableTarget, DataflowOptions.Default, batchSize, cacheSize)
66 | {
67 | }
68 |
69 | protected override void OnSuccessfulLookup(OrderEx input, IDimRow rowInDimTable)
70 | {
71 | //what we want is just the key column of the dimension row
72 | input.ProductKey = rowInDimTable.AutoIncrementKey;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/PeopleFlow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo
8 | {
9 | using System.Threading;
10 | using System.Threading.Tasks.Dataflow;
11 |
12 | using Gridsum.DataflowEx.Databases;
13 |
14 | using Newtonsoft.Json;
15 |
16 | public class Person : IEventProvider
17 | {
18 | [DBColumnMapping("PersonTarget", "NameCol", "N/A", ColumnMappingOption.Mandatory)]
19 | [DBColumnMapping("OrderTarget", "CustomerName", "Unknown Customer", ColumnMappingOption.Optional)]
20 | public string Name { get; set; }
21 |
22 | [DBColumnMapping("PersonTarget", "AgeCol", -1, ColumnMappingOption.Optional)]
23 | [DBColumnMapping("OrderTarget", "CustomerAge", -1, ColumnMappingOption.Optional)]
24 | public int? Age { get; set; }
25 |
26 | public DataflowEvent GetEvent()
27 | {
28 | if (Age > 70)
29 | {
30 | return new DataflowEvent("OldPerson");
31 | }
32 | else
33 | {
34 | //returning empty so it will not be recorded as an event
35 | return DataflowEvent.Empty;
36 | }
37 | }
38 | }
39 |
40 | public class PeopleFlow : Dataflow
41 | {
42 | private TransformBlock m_converter;
43 | private TransformBlock m_recorder;
44 | private StatisticsRecorder m_peopleRecorder;
45 |
46 | public PeopleFlow(DataflowOptions dataflowOptions)
47 | : base(dataflowOptions)
48 | {
49 | m_peopleRecorder = new StatisticsRecorder(this) { Name = "PeopleRecorder"};
50 |
51 | m_converter = new TransformBlock(s => JsonConvert.DeserializeObject(s));
52 | m_recorder = new TransformBlock(
53 | p =>
54 | {
55 | //record every person
56 | m_peopleRecorder.Record(p);
57 | return p;
58 | });
59 |
60 | m_converter.LinkTo(m_recorder, new DataflowLinkOptions() { PropagateCompletion = true});
61 |
62 | RegisterChild(m_converter);
63 | RegisterChild(m_recorder);
64 | }
65 |
66 | public override ITargetBlock InputBlock { get { return m_converter; } }
67 | public override ISourceBlock OutputBlock { get { return m_recorder; } }
68 | public StatisticsRecorder PeopleRecorder { get { return m_peopleRecorder; } }
69 | }
70 | }
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Demo/SlowFlow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Demo
8 | {
9 | using System.Threading;
10 | using System.Threading.Tasks.Dataflow;
11 |
12 | public class SlowFlow : Dataflow
13 | {
14 | private Dataflow _splitter;
15 | private Dataflow _printer;
16 |
17 | public SlowFlow(DataflowOptions dataflowOptions)
18 | : base(dataflowOptions)
19 | {
20 | _splitter = new TransformManyBlock(new Func>(this.SlowSplit),
21 | dataflowOptions.ToExecutionBlockOption())
22 | .ToDataflow(dataflowOptions, "SlowSplitter");
23 |
24 | _printer = new ActionBlock(c => Console.WriteLine(c),
25 | dataflowOptions.ToExecutionBlockOption())
26 | .ToDataflow(dataflowOptions, "Printer");
27 |
28 | RegisterChild(_splitter);
29 | RegisterChild(_printer);
30 |
31 | _splitter.LinkTo(_printer);
32 | }
33 |
34 | private IEnumerable SlowSplit(string s)
35 | {
36 | foreach (var c in s)
37 | {
38 | Thread.Sleep(1000); //slow down
39 | yield return c;
40 | }
41 | }
42 |
43 | public override ITargetBlock InputBlock { get { return _splitter.InputBlock; } }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/BlockExtensionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using System.Threading.Tasks.Dataflow;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Gridsum.DataflowEx.Test
9 | {
10 | [TestClass]
11 | public class BlockExtensionTest
12 | {
13 | [TestMethod]
14 | public async Task TestBlockBufferCount()
15 | {
16 | var block1 = new TransformBlock(i => 2 * i);
17 | var block2 = new TransformManyBlock(i => new [] { i });
18 | var block3 = new ActionBlock(i => { Thread.Sleep(1000); });
19 |
20 | block1.Post(0);
21 | block2.Post(0);
22 | block2.Post(0);
23 | block3.Post(0);
24 | block3.Post(0);
25 | block3.Post(0);
26 |
27 | await Task.Delay(200);
28 |
29 | Assert.AreEqual(1, block1.GetBufferCount().Total());
30 | Assert.AreEqual(2, block2.GetBufferCount().Total());
31 | Assert.AreEqual(2, block3.GetBufferCount().Total());
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DatabaseTests/DbColumnPathTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test.DatabaseTests
8 | {
9 | using Dapper;
10 |
11 | using Gridsum.DataflowEx.Databases;
12 | using Gridsum.DataflowEx.Test.Demo;
13 |
14 | using Microsoft.VisualStudio.TestTools.UnitTesting;
15 |
16 | [TestClass]
17 | public class DbColumnPathTest
18 | {
19 | public class PersonEx
20 | {
21 | [DBColumnMapping("PersonTarget", "NameCol", "N/A", ColumnMappingOption.Optional)]
22 | public string Name { get; set; }
23 |
24 | [DBColumnMapping("PersonTarget", "AgeCol", -1, ColumnMappingOption.Optional)]
25 | public int? Age { get; set; }
26 |
27 | [DBColumnPath(DBColumnPathOptions.DoNotGenerateNullCheck)]
28 | [DBColumnPath(DBColumnPathOptions.DoNotExpand)]
29 | public Person Me { get; set; }
30 | }
31 |
32 | public class PersonEx2
33 | {
34 | [DBColumnMapping("PersonTarget", "NameCol", "N/A", ColumnMappingOption.Optional)]
35 | public string Name { get; set; }
36 |
37 | [DBColumnMapping("PersonTarget", "AgeCol", -1, ColumnMappingOption.Optional)]
38 | public int? Age { get; set; }
39 |
40 | [DBColumnPath(DBColumnPathOptions.DoNotGenerateNullCheck)]
41 | public Person Me { get; set; }
42 | }
43 |
44 | public class Person
45 | {
46 | [DBColumnMapping("PersonTarget", "NameCol", "N/A", ColumnMappingOption.Overwrite)]
47 | public string Name { get; set; }
48 |
49 | [DBColumnMapping("PersonTarget", "AgeCol", -1, ColumnMappingOption.Overwrite)]
50 | public int? Age { get; set; }
51 | }
52 |
53 | //should access level 1
54 | [TestMethod]
55 | public async Task TestDoNoExpand()
56 | {
57 | string connStr = ExternalMappingTest.CreatePeopleTable(4);
58 |
59 | var dbInserter = new DbBulkInserter(connStr, "dbo.People", DataflowOptions.Default, "PersonTarget");
60 |
61 | dbInserter.Post(new PersonEx { Age = 10, Name = "Aaron", Me = new Person { Age = 20, Name = "Bob" } });
62 | await dbInserter.SignalAndWaitForCompletionAsync();
63 |
64 | using (var conn = TestUtils.GetLocalDB("ExternalMappingTest4"))
65 | {
66 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People"));
67 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People where NameCol = 'Aaron'"));
68 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People where AgeCol = 10"));
69 | }
70 | }
71 |
72 | //should access level 2
73 | [TestMethod]
74 | public async Task TestDoNoExpand2()
75 | {
76 | string connStr = ExternalMappingTest.CreatePeopleTable(4);
77 |
78 | var dbInserter = new DbBulkInserter(connStr, "dbo.People", DataflowOptions.Default, "PersonTarget");
79 |
80 | dbInserter.Post(new PersonEx2 { Age = 10, Name = "Aaron", Me = new Person { Age = 20, Name = "Bob" } });
81 | await dbInserter.SignalAndWaitForCompletionAsync();
82 |
83 | using (var conn = TestUtils.GetLocalDB("ExternalMappingTest4"))
84 | {
85 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People"));
86 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People where NameCol = 'Bob'"));
87 | Assert.AreEqual(1, conn.ExecuteScalar("select count(*) from dbo.People where AgeCol = 20"));
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DatabaseTests/GSProduct.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Test.DatabaseTests
2 | {
3 | class GSProduct
4 | {
5 | public const string WD = ("WD");
6 | public const string CD = ("CD");
7 | public const string SEMD = ("SEMD");
8 | public const string MD = ("MD");
9 | public const string VD = ("VD");
10 | public const string SD = ("SD");
11 | public const string SEOD = ("SEOD");
12 | public const string AD = ("AD");
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DatabaseTests/NestClassTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.SqlClient;
3 | using System.Linq;
4 | using Gridsum.DataflowEx.Databases;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Gridsum.DataflowEx.Test.DatabaseTests
9 | {
10 | public class A
11 | {
12 | public int Id { get; set; }
13 |
14 | [DBColumnMapping("NestClassTest", "Value1", "-")]
15 | public string Value1 { get; set; }
16 | }
17 |
18 | public class AEF : A
19 | {
20 | public string Value2 { get; set; }
21 | }
22 |
23 | public class ABulk : A
24 | {
25 | public B B { get; set; }
26 | }
27 |
28 | public class B
29 | {
30 | [DBColumnMapping("NestClassTest", "Value2", "--")]
31 | public string Value2 { get; set; }
32 | }
33 |
34 | [TestClass]
35 | public class NestClassTest
36 | {
37 | [TestMethod]
38 | public void TestNestClass()
39 | {
40 | var connectString = TestUtils.GetLocalDBConnectionString();
41 | var context = new NestContext(connectString);
42 | context.NestInits.Add(new NestInit());
43 | context.SaveChanges();
44 |
45 | var a1 = new ABulk {};
46 | var a2 = new ABulk {B = new B { } };
47 | var a3 = new ABulk {B = new B { Value2 = "a" } };
48 | var a4 = new ABulk {Value1 = "a", B = new B { Value2 = "a" } };
49 |
50 | using (var connection = new SqlConnection(connectString))
51 | {
52 | connection.Open();
53 | var aTypeAccessor = TypeAccessorManager.GetAccessorForTable(new TargetTable("NestClassTest", connectString, "dbo.AAs"));
54 | var aReader = new BulkDataReader(aTypeAccessor, new[] { a1, a2, a3, a4 });
55 | using (var aCopy = new SqlBulkCopy(connection))
56 | {
57 | foreach (var map in aReader.ColumnMappings)
58 | {
59 | aCopy.ColumnMappings.Add(map);
60 | }
61 | aCopy.DestinationTableName = "dbo.AAs";
62 | aCopy.WriteToServer(aReader);
63 | }
64 | }
65 |
66 | Assert.AreEqual(1, context.AAs.FirstOrDefault().Id);
67 | Assert.AreEqual(1, context.AAs.Count(a => a.Value1 == "a"));
68 | Assert.AreEqual(3, context.AAs.Count(a => a.Value1 == "-"));
69 | Assert.AreEqual(2, context.AAs.Count(a => a.Value2 == "a"));
70 | Assert.AreEqual(2, context.AAs.Count(a => a.Value2 == "--"));
71 | }
72 | }
73 |
74 | public class NestContext : DbContextBase
75 | {
76 | public NestContext(string config)
77 | : base(config)
78 | {
79 | }
80 |
81 | public DbSet NestInits { get; set; }
82 | public DbSet AAs { get; set; }
83 | }
84 |
85 | public class NestInit
86 | {
87 | public int Id { get; set; }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DatabaseTests/ReferenceLoopDepthTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Data.SqlClient;
3 | using System.Linq;
4 | using Gridsum.DataflowEx.Databases;
5 | using Microsoft.EntityFrameworkCore;
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 |
8 | namespace Gridsum.DataflowEx.Test.DatabaseTests
9 | {
10 | public class LoopBase
11 | {
12 | public int Id { get; set; }
13 | public string Name { get; set; }
14 | }
15 |
16 | public class LoopA : LoopBase
17 | {
18 | public LoopB LoopB { get; set; }
19 | }
20 |
21 | public class LoopB : LoopBase
22 | {
23 | public LoopC LoopC { get; set; }
24 | }
25 |
26 | public class LoopC
27 | {
28 | public LoopA LoopA { get; set; }
29 | }
30 | ///
31 | /// 主要测试当对象引用有闭环时,以及多个属性同时对应到数据库列时。是否能正常
32 | ///
33 |
34 | [TestClass]
35 | public class ReferenceLoopDepthTest
36 | {
37 | [TestMethod]
38 | public void TestReferenceLoopDepth()
39 | {
40 | var connectString = TestUtils.GetLocalDBConnectionString();
41 | //init database
42 | var context = new LoopContext(connectString);
43 | context.LoopInits.Add(new LoopInit());
44 | context.SaveChanges();
45 |
46 | var loopA = new LoopA
47 | {
48 | Name = "loopA",
49 | LoopB = new LoopB
50 | {
51 | Name = "loopB",
52 | LoopC = new LoopC
53 | {
54 | LoopA = new LoopA
55 | {
56 | Name = "loopA2"
57 | }
58 | }
59 | }
60 | };
61 |
62 | using (var connection = new SqlConnection(connectString))
63 | {
64 | connection.Open();
65 |
66 | var tableName = "dbo.LoopEntities";
67 | var accessor = TypeAccessorManager.GetAccessorForTable(new TargetTable(GSProduct.AD, connectString, tableName));
68 | var reader = new BulkDataReader(accessor, new[] { loopA });
69 | using (var bulkCopy = new SqlBulkCopy(connection))
70 | {
71 | foreach (SqlBulkCopyColumnMapping map in reader.ColumnMappings)
72 | {
73 | bulkCopy.ColumnMappings.Add(map);
74 | }
75 | bulkCopy.DestinationTableName = tableName;
76 |
77 | bulkCopy.WriteToServer(reader);
78 | }
79 | }
80 |
81 | Assert.AreEqual("loopA", context.LoopEntities.FirstOrDefault().Name);
82 |
83 | }
84 | }
85 |
86 |
87 | public class LoopContext : DbContextBase
88 | {
89 | public LoopContext(string con) : base(con) { }
90 |
91 | public DbSet LoopInits { get; set; }
92 |
93 | public DbSet LoopEntities { get; set; }
94 | }
95 |
96 | public class LoopInit
97 | {
98 | public int Id { get; set; }
99 | }
100 |
101 | public class LoopEntity
102 | {
103 | public int Id { get; set; }
104 |
105 | public string Name { get; set; }
106 |
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DatabaseTests/TestBootstrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | using Docker.DotNet;
4 | using Docker.DotNet.Models;
5 | using System.Threading;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.Text;
9 |
10 | namespace Gridsum.DataflowEx.Test.DatabaseTests
11 | {
12 | [TestClass]
13 | public class TestBootstrapper
14 | {
15 | internal static string s_containerName = "mssql-for-dataflowex-test";
16 | internal static string s_saPassword = "UpLow123-+";
17 | internal static string s_imageName = "microsoft/mssql-server-linux:2017-GA";
18 | internal static string s_runningSqlServerContainerID;
19 | internal static Process s_sqlserverDockerProcess;
20 |
21 | [AssemblyInitialize]
22 | public static void BootSqlServerWithDockerCli(TestContext tc)
23 | {
24 | BootSqlServerWithDockerCli(tc, s_containerName);
25 | }
26 |
27 | public static void BootSqlServerWithDockerCli(TestContext tc, string containerName)
28 | {
29 | //using (var pullProcess = ExecuteCommand("docker", $"pull {s_imageName}", ".", Console.WriteLine, Console.WriteLine))
30 | //{
31 | // pullProcess.WaitForExit();
32 | //}
33 |
34 | s_sqlserverDockerProcess = ExecuteCommand("docker", $"run --name \"{containerName}\" -e \"ACCEPT_EULA=Y\" -e \"SA_PASSWORD={s_saPassword}\" -a stdin -a stdout -a stderr -p 1433:1433 {s_imageName}", ".", Console.WriteLine, Console.WriteLine);
35 | Thread.Sleep(20 * 1000); //todo: improve this
36 | }
37 |
38 | [AssemblyCleanup]
39 | public static void ShutdownSqlServerWithDockerCli()
40 | {
41 | ShutdownSqlServerWithDockerCli(true, s_containerName);
42 | }
43 |
44 | public static void ShutdownSqlServerWithDockerCli(bool removeAfterShutDown, string containerName)
45 | {
46 | using (var process = ExecuteCommand("docker", $"stop \"{containerName}\"", ".", Console.WriteLine, Console.WriteLine))
47 | {
48 | process.WaitForExit();
49 | }
50 |
51 | s_sqlserverDockerProcess.Dispose();
52 |
53 | if (removeAfterShutDown)
54 | {
55 | using (var process2 = ExecuteCommand("docker", $"rm \"{containerName}\"", ".", Console.WriteLine, Console.WriteLine))
56 | {
57 | process2.WaitForExit();
58 | }
59 | }
60 | }
61 |
62 | #region docker client approach (not working)
63 | public static void BootSqlServerWithDockerClient(TestContext tc)
64 | {
65 | DockerClient client = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();
66 | var response = client.Containers.CreateContainerAsync(new CreateContainerParameters {
67 | Image = "microsoft/mssql-server-linux",
68 | AttachStderr = true,
69 | AttachStdin = true,
70 | AttachStdout = true,
71 | Env = new[] { "ACCEPT_EULA=Y", $"SA_PASSWORD={s_saPassword}" },
72 | ExposedPorts = new Dictionary() {
73 | { "1433/tcp", new EmptyStruct() }
74 | },
75 | HostConfig = new HostConfig
76 | {
77 | PortBindings = new Dictionary> {
78 | {
79 | "1433/tcp", new List {
80 | new PortBinding { HostPort = 1433.ToString() }
81 | }
82 | }
83 | }
84 | }
85 |
86 | }).Result;
87 | s_runningSqlServerContainerID = response.ID;
88 | client.Containers.StartContainerAsync(s_runningSqlServerContainerID, new ContainerStartParameters { }).Wait();
89 | }
90 |
91 | public static void ShutdownSqlServerDockerImage()
92 | {
93 | DockerClient client = new DockerClientConfiguration(new Uri("npipe://./pipe/docker_engine")).CreateClient();
94 | client.Containers.StopContainerAsync(s_runningSqlServerContainerID, new ContainerStopParameters {
95 | WaitBeforeKillSeconds = 60
96 | }, new CancellationToken()).Wait();
97 | }
98 | #endregion
99 |
100 | static Process ExecuteCommand(string executable, string arguments, string workingDirectory, Action output, Action error)
101 | {
102 | var process = new Process();
103 | process.StartInfo.FileName = executable;
104 | process.StartInfo.Arguments = arguments;
105 | process.StartInfo.WorkingDirectory = workingDirectory;
106 | process.StartInfo.UseShellExecute = false;
107 | process.StartInfo.CreateNoWindow = true;
108 | process.StartInfo.RedirectStandardOutput = true;
109 | process.StartInfo.RedirectStandardError = true;
110 | process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
111 | process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
112 |
113 | process.OutputDataReceived += (sender, e) =>
114 | {
115 | output(e.Data);
116 | };
117 |
118 | process.ErrorDataReceived += (sender, e) =>
119 | {
120 | error(e.Data);
121 | };
122 |
123 | process.Start();
124 |
125 | process.BeginOutputReadLine();
126 | process.BeginErrorReadLine();
127 |
128 | return process;
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/DataflowFaultTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 |
4 | namespace Gridsum.DataflowEx.Test
5 | {
6 | using System.Threading.Tasks;
7 | using System.Threading.Tasks.Dataflow;
8 |
9 | using Gridsum.DataflowEx.Exceptions;
10 |
11 | [TestClass]
12 | public class DataflowFaultTest
13 | {
14 | public class MyDataflow : Dataflow
15 | {
16 | public Dataflow m_block1;
17 | public Dataflow m_block2;
18 | public int cleanUpSignal = 0;
19 |
20 | public MyDataflow(): base(DataflowOptions.Default)
21 | {
22 | m_block1 = new BufferBlock().ToDataflow(name: "innerflow1");
23 | m_block2 = new BatchBlock(100).ToDataflow(name: "innerflow2");
24 | m_block1.LinkTo(m_block2);
25 | RegisterChild(this.m_block1);
26 | RegisterChild(this.m_block2);
27 | }
28 |
29 | public override ITargetBlock InputBlock
30 | {
31 | get
32 | {
33 | return this.m_block1.InputBlock;
34 | }
35 | }
36 |
37 | protected override void CleanUp(Exception dataflowException)
38 | {
39 | if (dataflowException == null)
40 | {
41 | cleanUpSignal = 1;
42 | }
43 | else
44 | {
45 | cleanUpSignal = -1;
46 | }
47 | }
48 | }
49 |
50 | [TestMethod]
51 | public async Task TestCleanUp()
52 | {
53 | var f = new MyDataflow();
54 | await f.SignalAndWaitForCompletionAsync();
55 | Assert.AreEqual(1, f.cleanUpSignal);
56 | }
57 |
58 | [TestMethod]
59 | public async Task TestCleanUpOnFault()
60 | {
61 | var f = new MyDataflow() {Name = "dataflow1"};
62 | f.m_block2.RegisterChild(new BufferBlock());
63 |
64 | Task.Run(
65 | async () =>
66 | {
67 | await Task.Delay(200);
68 | (f.m_block2.InputBlock).Fault(new InvalidOperationException());
69 | });
70 |
71 | var f2 = new Dataflow(DataflowOptions.Default) {Name = "dataflow2"};
72 | f2.RegisterChild(f.m_block1);
73 | f2.RegisterChild(f.m_block2);
74 |
75 | var f3 = new Dataflow(DataflowOptions.Default) { Name = "dataflow3" };
76 | f3.RegisterChild(f);
77 | f3.RegisterChild(f2);
78 |
79 | await this.AssertFlowError(f);
80 |
81 | Assert.AreEqual(-1, f.cleanUpSignal);
82 |
83 | await this.AssertFlowError(f2);
84 |
85 | await this.AssertFlowError(f3);
86 | }
87 |
88 | public async Task AssertFlowError(Dataflow f) where T : Exception
89 | {
90 | try
91 | {
92 | await f.CompletionTask;
93 | }
94 | catch (Exception e)
95 | {
96 | Assert.IsTrue(TaskEx.UnwrapWithPriority(e as AggregateException) is T);
97 | }
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/Demo/Order.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test.Demo
8 | {
9 | using System.Linq.Expressions;
10 |
11 | using Gridsum.DataflowEx.Databases;
12 | using Gridsum.DataflowEx.ETL;
13 |
14 | public class Order
15 | {
16 | [DBColumnMapping("OrderTarget", "Date", null)]
17 | public DateTime OrderDate { get; set; }
18 |
19 | [DBColumnMapping("OrderTarget", "Value", 0.0f)]
20 | public float? OrderValue { get; set; }
21 |
22 | [DBColumnPath(DBColumnPathOptions.DoNotGenerateNullCheck)]
23 | public Person Customer { get; set; }
24 | }
25 |
26 | public class OrderEx : Order
27 | {
28 | public OrderEx()
29 | {
30 | this.OrderDate = DateTime.Now;
31 | }
32 |
33 | // This is the field we want to populate
34 | [DBColumnMapping("LookupDemo", "ProductKey")]
35 | [DBColumnMapping("OrderTarget", "ProductKey")]
36 | public long? ProductKey { get; set; }
37 |
38 | public Product Product { get; set; }
39 | }
40 |
41 | public class Product
42 | {
43 | public Product(string category, string name)
44 | {
45 | this.Category = category;
46 | this.Name = name;
47 | }
48 |
49 | [DBColumnMapping("LookupDemo", "Category")]
50 | public string Category { get; private set; }
51 |
52 | [DBColumnMapping("LookupDemo", "ProductName")]
53 | public string Name { get; private set; }
54 |
55 | [DBColumnMapping("LookupDemo", "ProductFullName")]
56 | public string FullName { get { return string.Format("{0}-{1}", Category, Name); } }
57 | }
58 |
59 | public class ProductLookupFlow : DbDataJoiner
60 | {
61 | public ProductLookupFlow(TargetTable dimTableTarget, int batchSize = 8192, int cacheSize = 1048576)
62 | : base(
63 | //Tells DbDataJoiner the join condition is 'OrderEx.Product.FullName = TABLE.ProductFullName'
64 | o => o.Product.FullName,
65 | dimTableTarget, DataflowOptions.Default, batchSize, cacheSize)
66 | {
67 | }
68 |
69 | protected override void OnSuccessfulLookup(OrderEx input, IDimRow rowInDimTable)
70 | {
71 | //what we want is just the key column of the dimension row
72 | input.ProductKey = rowInDimTable.AutoIncrementKey;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/Demo/PeopleFlow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test.Demo
8 | {
9 | using System.Threading;
10 | using System.Threading.Tasks.Dataflow;
11 |
12 | using Gridsum.DataflowEx.Databases;
13 |
14 | using Newtonsoft.Json;
15 |
16 | public class Person : IEventProvider
17 | {
18 | [DBColumnMapping("PersonTarget", "NameCol", "N/A", ColumnMappingOption.Mandatory)]
19 | [DBColumnMapping("OrderTarget", "CustomerName", "Unknown Customer", ColumnMappingOption.Optional)]
20 | public string Name { get; set; }
21 |
22 | [DBColumnMapping("PersonTarget", "AgeCol", -1, ColumnMappingOption.Optional)]
23 | [DBColumnMapping("OrderTarget", "CustomerAge", -1, ColumnMappingOption.Optional)]
24 | public int? Age { get; set; }
25 |
26 | public DataflowEvent GetEvent()
27 | {
28 | if (Age > 70)
29 | {
30 | return new DataflowEvent("OldPerson");
31 | }
32 | else
33 | {
34 | //returning empty so it will not be recorded as an event
35 | return DataflowEvent.Empty;
36 | }
37 | }
38 | }
39 |
40 | public class PeopleFlow : Dataflow
41 | {
42 | private TransformBlock m_converter;
43 | private TransformBlock m_recorder;
44 | private StatisticsRecorder m_peopleRecorder;
45 |
46 | public PeopleFlow(DataflowOptions dataflowOptions)
47 | : base(dataflowOptions)
48 | {
49 | m_peopleRecorder = new StatisticsRecorder(this) { Name = "PeopleRecorder"};
50 |
51 | m_converter = new TransformBlock(s => JsonConvert.DeserializeObject(s));
52 | m_recorder = new TransformBlock(
53 | p =>
54 | {
55 | //record every person
56 | m_peopleRecorder.Record(p);
57 | return p;
58 | });
59 |
60 | m_converter.LinkTo(m_recorder, new DataflowLinkOptions() { PropagateCompletion = true});
61 |
62 | RegisterChild(m_converter);
63 | RegisterChild(m_recorder);
64 | }
65 |
66 | public override ITargetBlock InputBlock { get { return m_converter; } }
67 | public override ISourceBlock OutputBlock { get { return m_recorder; } }
68 | public StatisticsRecorder PeopleRecorder { get { return m_peopleRecorder; } }
69 | }
70 | }
71 |
72 |
73 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/Demo/SlowFlow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test.Demo
8 | {
9 | using System.Threading;
10 | using System.Threading.Tasks.Dataflow;
11 |
12 | public class SlowFlow : Dataflow
13 | {
14 | private Dataflow _splitter;
15 | private Dataflow _printer;
16 |
17 | public SlowFlow(DataflowOptions dataflowOptions)
18 | : base(dataflowOptions)
19 | {
20 | _splitter = new TransformManyBlock(new Func>(this.SlowSplit),
21 | dataflowOptions.ToExecutionBlockOption())
22 | .ToDataflow(dataflowOptions, "SlowSplitter");
23 |
24 | _printer = new ActionBlock(c => Console.WriteLine(c),
25 | dataflowOptions.ToExecutionBlockOption())
26 | .ToDataflow(dataflowOptions, "Printer");
27 |
28 | RegisterChild(_splitter);
29 | RegisterChild(_printer);
30 |
31 | _splitter.LinkTo(_printer);
32 | }
33 |
34 | private IEnumerable SlowSplit(string s)
35 | {
36 | foreach (var c in s)
37 | {
38 | Thread.Sleep(1000); //slow down
39 | yield return c;
40 | }
41 | }
42 |
43 | public override ITargetBlock InputBlock { get { return _splitter.InputBlock; } }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/ETL/TestDataJoiner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test.ETL
8 | {
9 | using System.Data;
10 | using System.Diagnostics;
11 | using System.Linq.Expressions;
12 | using System.Threading.Tasks.Dataflow;
13 |
14 | using Gridsum.DataflowEx.Databases;
15 | using Gridsum.DataflowEx.ETL;
16 | using Gridsum.DataflowEx.Test.DatabaseTests;
17 | using Microsoft.EntityFrameworkCore;
18 | using Microsoft.VisualStudio.TestTools.UnitTesting;
19 |
20 | [TestClass]
21 | public class TestDataJoiner
22 | {
23 | class JoinerWithAsserter : DbDataJoiner
24 | {
25 | public int Count;
26 |
27 | public JoinerWithAsserter(Expression> joinOn, TargetTable dimTableTarget, int batchSize)
28 | : base(joinOn, dimTableTarget, DataflowOptions.Default, batchSize)
29 | {
30 | this.Count = 0;
31 | }
32 |
33 | protected override void OnSuccessfulLookup(Trunk input, IDimRow rowInDimTable)
34 | {
35 | Assert.AreEqual(input.Pointer, rowInDimTable.JoinOn);
36 | //Assert.AreEqual("Str" + input.Pointer, rowInDimTable["StrValue"]);
37 | this.Count++;
38 | }
39 | }
40 |
41 | [TestMethod]
42 | public async Task TestDataJoinerJoining()
43 | {
44 | var connectString = TestUtils.GetLocalDBConnectionString("TestDataJoinerJoining");
45 | var context = new InsertContext(connectString);
46 |
47 | for (int i = 0; i < 10; i++)
48 | {
49 | context.Leafs.Add(new Leaf() {IntValue = i, Key = i.ToString(), StrValue = "Str" + i});
50 | }
51 |
52 | context.SaveChanges();
53 |
54 | var joiner = new JoinerWithAsserter(
55 | t => t.Pointer,
56 | new TargetTable(Leaf.DimLeaf, connectString, "Leafs"),
57 | 2);
58 |
59 | joiner.DataflowOptions.MonitorInterval = TimeSpan.FromSeconds(3);
60 | joiner.LinkLeftToNull();
61 |
62 | var dataArray = new[]
63 | {
64 | new Trunk() { Pointer = "3" },
65 | new Trunk() { Pointer = "5" },
66 | new Trunk() { Pointer = "7" },
67 | new Trunk() { Pointer = "7" },
68 | new Trunk() { Pointer = "9" },
69 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
70 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
71 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
72 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
73 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
74 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
75 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
76 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
77 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
78 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
79 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
80 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
81 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
82 | new Trunk() { Pointer = "99", Leaf = new Leaf() { StrValue = "Str99" } },
83 | };
84 |
85 | await joiner.ProcessAsync(dataArray);
86 |
87 | Assert.AreEqual(dataArray.Length, joiner.Count);
88 | }
89 | }
90 |
91 | public class Trunk
92 | {
93 | public int Id { get; set; }
94 |
95 | [DBColumnMapping(Leaf.DimLeaf, "Key")]
96 | public string Pointer { get; set; }
97 |
98 | public string Name { get; set; }
99 | public Leaf Leaf { get; set; }
100 | }
101 |
102 | public class Leaf
103 | {
104 | public const string DimLeaf = "DimLeaf";
105 |
106 | public int Id { get; set; }
107 |
108 | public string Key { get; set; }
109 |
110 | [DBColumnMapping(DimLeaf)]
111 | public string StrValue { get; set; }
112 |
113 | [DBColumnMapping(DimLeaf)]
114 | public int IntValue { get; set; }
115 | }
116 |
117 | public class InsertContext : DbContextBase
118 | {
119 | public InsertContext(string conn)
120 | : base(conn)
121 | {
122 | }
123 |
124 | public DbSet Trunks { get; set; }
125 | public DbSet Leafs { get; set; }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/ETL/TestDataJoiner2.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Test.ETL.Test2
2 | {
3 | using System;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Linq.Expressions;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | using Gridsum.DataflowEx.Databases;
11 | using Gridsum.DataflowEx.ETL;
12 | using Microsoft.EntityFrameworkCore;
13 | using Microsoft.VisualStudio.TestTools.UnitTesting;
14 |
15 | [TestClass]
16 | public class TestDataJoiner2
17 | {
18 | class JoinerWithAsserter : DbDataJoiner
19 | {
20 | public int Count;
21 |
22 | public JoinerWithAsserter(Expression> joinOn, TargetTable dimTableTarget, int batchSize)
23 | : base(joinOn, dimTableTarget, DataflowOptions.Default, batchSize)
24 | {
25 | this.Count = 0;
26 | }
27 |
28 | protected override void OnSuccessfulLookup(Trunk input, IDimRow rowInDimTable)
29 | {
30 | Assert.IsTrue(TestUtils.ArrayEqual(input.Pointer, rowInDimTable.JoinOn));
31 | //Assert.AreEqual("Str" + Encoding.UTF8.GetString(input.Pointer), rowInDimTable["StrValue"]);
32 | this.Count++;
33 | }
34 | }
35 |
36 | [TestMethod]
37 | public async Task TestDataJoinerJoining()
38 | {
39 | var connectString = TestUtils.GetLocalDBConnectionString("TestDataJoinerJoining");
40 | var context = new InsertContext(connectString);
41 |
42 | for (int i = 0; i < 10; i++)
43 | {
44 | context.Leafs.Add(new Leaf() { IntValue = i, Key = i.ToString().ToByteArray(), StrValue = "Str" + i });
45 | }
46 |
47 | context.SaveChanges();
48 |
49 | var joiner = new JoinerWithAsserter(
50 | t => t.Pointer,
51 | new TargetTable(Leaf.DimLeaf, connectString, "Leafs"),
52 | 2);
53 |
54 | joiner.DataflowOptions.MonitorInterval = TimeSpan.FromSeconds(3);
55 |
56 | joiner.LinkLeftToNull();
57 |
58 | var dataArray = new[]
59 | {
60 | new Trunk() { Pointer = "3".ToByteArray() },
61 | new Trunk() { Pointer = "5".ToByteArray() },
62 | new Trunk() { Pointer = "7".ToByteArray() },
63 | new Trunk() { Pointer = "7".ToByteArray() },
64 | new Trunk() { Pointer = "9".ToByteArray() },
65 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
66 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
67 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
68 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
69 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
70 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
71 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
72 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
73 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
74 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
75 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
76 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
77 | new Trunk() { Pointer = "99".ToByteArray(), Leaf = new Leaf() { StrValue = "Str99" } },
78 | };
79 |
80 | await joiner.ProcessAsync(dataArray);
81 |
82 | Assert.AreEqual(dataArray.Length, joiner.Count);
83 |
84 | Assert.AreEqual(11, context.Leafs.Count());
85 | }
86 | }
87 |
88 |
89 | public class Trunk
90 | {
91 | public int Id { get; set; }
92 |
93 | [DBColumnMapping(Leaf.DimLeaf, "Key")]
94 | public byte[] Pointer { get; set; }
95 |
96 | public string Name { get; set; }
97 | public Leaf Leaf { get; set; }
98 | }
99 |
100 | public class Leaf
101 | {
102 | public const string DimLeaf = "DimLeaf";
103 |
104 | public int Id { get; set; }
105 |
106 | public byte[] Key { get; set; }
107 |
108 | [DBColumnMapping(DimLeaf)]
109 | public string StrValue { get; set; }
110 |
111 | [DBColumnMapping(DimLeaf)]
112 | public int IntValue { get; set; }
113 | }
114 |
115 | public class InsertContext : DbContextBase
116 | {
117 | public InsertContext(string conn)
118 | : base(conn)
119 | {
120 | }
121 |
122 | public DbSet Trunks { get; set; }
123 | public DbSet Leafs { get; set; }
124 | }
125 | }
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/Gridsum.DataflowEx.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestAutoComplete.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using System.Threading.Tasks.Dataflow;
6 | using Gridsum.DataflowEx.AutoCompletion;
7 | using Microsoft.VisualStudio.TestTools.UnitTesting;
8 |
9 | namespace Gridsum.DataflowEx.Test
10 | {
11 | [TestClass]
12 | public class TestAutoComplete
13 | {
14 | public class Int : TracableItemBase
15 | {
16 | public int Val { get; set; }
17 | }
18 |
19 | [TestMethod]
20 | public async Task TestAutoComplete1()
21 | {
22 | var block = new TransformManyBlock(i =>
23 | {
24 | int j = i.Val + 1;
25 | Console.WriteLine("block2: i = {0}, j = {1}", i.Val, j);
26 | Thread.Sleep(500);
27 | if (j < 10) return new[] { new Int { Val = j } };
28 | else return Enumerable.Empty();
29 | });
30 |
31 | var dataflow1 = DataflowUtils.FromDelegate(i => i);
32 | var dataflow2 = DataflowUtils.FromBlock(block).AutoComplete(TimeSpan.FromSeconds(1));
33 |
34 | dataflow1.GoTo(dataflow2).GoTo(dataflow1);
35 |
36 | dataflow1.InputBlock.Post(new Int() { Val = 1 });
37 | Assert.IsTrue(await dataflow2.CompletionTask.FinishesIn(TimeSpan.FromSeconds(10)));
38 | }
39 |
40 | [TestMethod]
41 | public async Task TestAutoComplete2()
42 | {
43 | var block2 = new TransformManyBlock(i =>
44 | {
45 | return Enumerable.Empty();
46 | });
47 |
48 | var dataflow1 = DataflowUtils.FromDelegate(i => new[] { i }); //preserve the guid
49 | var dataflow2 = DataflowUtils.FromBlock(block2).AutoComplete(TimeSpan.FromSeconds(1));
50 |
51 | dataflow1.GoTo(dataflow2).GoTo(dataflow1);
52 |
53 | dataflow1.InputBlock.Post(new Int() { Val = 1 });
54 | Assert.IsTrue(await dataflow2.CompletionTask.FinishesIn(TimeSpan.FromSeconds(2)));
55 | Assert.IsTrue(dataflow2.Name.EndsWith("AutoComplete"));
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestDataBroadcaster.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Threading.Tasks.Dataflow;
7 | using Gridsum.DataflowEx.Exceptions;
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 |
10 | namespace Gridsum.DataflowEx.Test
11 | {
12 | [TestClass]
13 | public class TestDataBroadcaster
14 | {
15 | [TestMethod]
16 | public async Task TestDataBroadcaster1()
17 | {
18 | var random = new Random();
19 | var dataCopier = new DataBroadcaster();
20 |
21 | int sum1 = 0;
22 | int sum2 = 0;
23 |
24 | var action1 = new ActionBlock(i => sum1 = sum1 + i);
25 | var action2 = new ActionBlock(i => sum2 = sum2 + i);
26 |
27 | dataCopier.LinkTo(DataflowUtils.FromBlock(action1));
28 | dataCopier.LinkTo(DataflowUtils.FromBlock(action2));
29 |
30 | for (int j = 0; j < 1000; j++)
31 | {
32 | dataCopier.InputBlock.Post((int) (random.NextDouble()*10000));
33 | }
34 |
35 | dataCopier.Complete();
36 |
37 | await TaskEx.AwaitableWhenAll(action1.Completion, action2.Completion);
38 |
39 | Console.WriteLine("sum1 = {0} , sum2 = {1}", sum1, sum2);
40 | Assert.AreEqual(sum1, sum2);
41 | }
42 |
43 | [TestMethod]
44 | public async Task TestDataBroadcaster2()
45 | {
46 | var random = new Random();
47 | var buffer = new BufferBlock();
48 |
49 | int sum1 = 0;
50 | int sum2 = 0;
51 | int sum3 = 0;
52 |
53 | var action1 = new ActionBlock(i => sum1 = sum1 + i);
54 | var action2 = new ActionBlock(i => sum2 = sum2 + i);
55 | var action3 = new ActionBlock(i => sum3 = sum3 + i);
56 |
57 | buffer.ToDataflow().LinkToMultiple(new []{action1, action2, action3}.Select(a => a.ToDataflow()).ToArray());
58 |
59 | for (int j = 0; j < 1000; j++)
60 | {
61 | buffer.Post((int)(random.NextDouble() * 10000));
62 | }
63 |
64 | buffer.Complete();
65 |
66 | await TaskEx.AwaitableWhenAll(action1.Completion, action2.Completion, action3.Completion);
67 |
68 | Console.WriteLine("sum1 = {0} , sum2 = {1}", sum1, sum2);
69 | Assert.AreEqual(sum1, sum2);
70 | Assert.AreEqual(sum1, sum3);
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestFlowBlocking.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Test
2 | {
3 | using System;
4 | using System.Threading.Tasks;
5 |
6 | using Gridsum.DataflowEx.Test.Demo;
7 |
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 |
10 | [TestClass]
11 | public class TestFlowBlocking
12 | {
13 | [TestMethod]
14 | public async Task TestBlocking1()
15 | {
16 | var slowFlow = new SlowFlow(new DataflowOptions
17 | {
18 | FlowMonitorEnabled = true,
19 | MonitorInterval = TimeSpan.FromSeconds(5),
20 | RecommendedCapacity = 2
21 | });
22 |
23 | Assert.IsTrue(slowFlow.Post("a"));
24 | Assert.IsTrue(slowFlow.Post("a"));
25 | Assert.IsFalse(slowFlow.Post("a"));
26 | Assert.IsFalse(slowFlow.Post("a"));
27 | Assert.IsFalse(slowFlow.Post("a"));
28 | Assert.IsFalse(slowFlow.Post("a"));
29 |
30 | await Task.Delay(2000);
31 |
32 | Assert.IsTrue(slowFlow.Post("a"));
33 | }
34 |
35 | [TestMethod]
36 | public async Task TestBlocking2()
37 | {
38 | var slowFlow = new SlowFlow(new DataflowOptions
39 | {
40 | FlowMonitorEnabled = true,
41 | MonitorInterval = TimeSpan.FromSeconds(5),
42 | RecommendedCapacity = 2
43 | });
44 |
45 | await slowFlow.SendAsync("a");
46 | await slowFlow.SendAsync("a");
47 | await slowFlow.SendAsync("a");
48 | await slowFlow.SendAsync("a");
49 | await slowFlow.SendAsync("a");
50 | await slowFlow.SendAsync("a");
51 |
52 | //Assert.IsTrue(slowFlow.Post("a"));
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestImmutable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 | using Microsoft.VisualStudio.TestTools.UnitTesting;
5 |
6 | namespace Gridsum.DataflowEx.Test
7 | {
8 | using System.Threading.Tasks;
9 |
10 | [TestClass]
11 | public class TestImmutable
12 | {
13 | [TestMethod]
14 | public void TestImmutableList()
15 | {
16 | var l = ImmutableList.Create(1, 3, 2);
17 |
18 | Assert.AreEqual(1, l.First());
19 | Assert.AreEqual(2, l.Last());
20 |
21 | l = l.Add(1);
22 |
23 | Assert.AreEqual(1, l.Last());
24 | }
25 |
26 | [TestMethod]
27 | public void TestListAdd()
28 | {
29 | const int c = 1000000;
30 | var l = ImmutableList.Empty;
31 |
32 | Parallel.For(0, c, i => { l = l.Add(i); });
33 |
34 | Assert.IsTrue(l.Count < c);
35 | Console.WriteLine(l.Count);
36 | }
37 |
38 | [TestMethod]
39 | public void TestListAdd2()
40 | {
41 | const int c = 1000000;
42 | var l = ImmutableList.Empty;
43 |
44 | Parallel.For(0, c, i => ImmutableUtils.AddOptimistically(ref l, i));
45 |
46 | Assert.IsTrue(l.Count == c);
47 | Console.WriteLine(l.Count);
48 | }
49 |
50 | [TestMethod]
51 | public void TestSetAdd()
52 | {
53 | var iset = ImmutableHashSet.Create(1,2,3);
54 |
55 | Assert.IsFalse(ImmutableUtils.TryAddOptimistically(ref iset, 3));
56 | Assert.IsTrue(ImmutableUtils.TryAddOptimistically(ref iset, 4));
57 | }
58 |
59 | [TestMethod]
60 | public void TestSetAdd2()
61 | {
62 | const int c = 1000000;
63 | var l = ImmutableHashSet.Empty;
64 |
65 | Parallel.For(0, c, i => Assert.IsTrue(ImmutableUtils.TryAddOptimistically(ref l, i)));
66 | Parallel.For(0, c, i => Assert.IsFalse(ImmutableUtils.TryAddOptimistically(ref l, i)));
67 |
68 | Assert.IsTrue(l.Count == c);
69 | Console.WriteLine(l.Count);
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestLogReader.cs:
--------------------------------------------------------------------------------
1 | namespace Gridsum.DataflowEx.Test
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using System.Threading.Tasks.Dataflow;
10 | using System.Xml.Serialization;
11 |
12 | using Microsoft.VisualStudio.TestTools.UnitTesting;
13 |
14 | ///
15 | /// Summary description for TestLogReader
16 | ///
17 | [TestClass]
18 | public class TestLogReader
19 | {
20 | #region Public Methods and Operators
21 |
22 | [TestMethod]
23 | public async Task TestMethod1()
24 | {
25 | var logReader = new SimpleLogReader(DataflowOptions.Default);
26 |
27 | long count = await logReader.ProcessAsync(new[] { "a", "a b", "a b c", "a b c d" }, false);
28 | long count2 = await logReader.ProcessAsync(new[] { "a", "a b", "a b c", "a b c d" });
29 |
30 | Assert.AreEqual(4 * 2, count + count2);
31 | Assert.AreEqual(4 * 2, logReader.Recorder[new DataflowEvent("a")]);
32 | Assert.AreEqual(3 * 2, logReader.Recorder[new DataflowEvent("b")]);
33 | Assert.AreEqual(2 * 2, logReader.Recorder[new DataflowEvent("c")]);
34 | Assert.AreEqual(1 * 2, logReader.Recorder[new DataflowEvent("d")]);
35 | }
36 |
37 | [TestMethod]
38 | public async Task TestMethod2()
39 | {
40 | var logReader = new SimpleLogReader(DataflowOptions.Default);
41 |
42 | try
43 | {
44 | long count = await logReader.ProcessAsync(this.GetStringSequence());
45 | Assert.Fail("shouldn't arrive here");
46 | }
47 | catch (Exception e)
48 | {
49 | Assert.AreEqual(typeof(OperationCanceledException), e.GetType());
50 | }
51 | }
52 | #endregion
53 |
54 | IEnumerable GetStringSequence()
55 | {
56 | yield return "a";
57 | yield return "b";
58 | yield return "ERROR";
59 | Thread.Sleep(5000);
60 | yield return "c";
61 | Thread.Sleep(5000);
62 | yield return "d";
63 | }
64 | }
65 |
66 | public class SimpleLogReader : LogReader
67 | {
68 | #region Fields
69 |
70 | private readonly TransformManyBlock m_parsingBlock;
71 |
72 | private readonly StatisticsRecorder m_recorder;
73 |
74 | private ActionBlock m_recordBlock;
75 |
76 | static readonly char[] Splitor = { ' ' };
77 |
78 | #endregion
79 |
80 | #region Constructors and Destructors
81 |
82 | public SimpleLogReader(DataflowOptions dataflowOptions)
83 | : base(dataflowOptions)
84 | {
85 | this.m_parsingBlock = new TransformManyBlock(
86 | s =>
87 | {
88 | if (string.IsNullOrEmpty(s))
89 | {
90 | return Enumerable.Empty();
91 | }
92 |
93 | if (s == "ERROR")
94 | {
95 | throw new InvalidDataException("evil data :)");
96 | }
97 |
98 | return s.Split(Splitor);
99 | });
100 |
101 | this.m_recorder = new StatisticsRecorder(this);
102 | this.m_recordBlock = new ActionBlock(
103 | s =>
104 | {
105 | if (s == "ERROR")
106 | {
107 | throw new InvalidDataException("evil data :)");
108 | }
109 |
110 | this.m_recorder.RecordEvent(s);
111 | });
112 |
113 | var df1 = DataflowUtils.FromBlock(m_parsingBlock);
114 | var df2 = DataflowUtils.FromBlock(m_recordBlock);
115 | df1.LinkTo(df2);
116 |
117 | RegisterChild(df1);
118 | RegisterChild(df2);
119 | }
120 |
121 | #endregion
122 |
123 | #region Public Properties
124 |
125 | public override ITargetBlock InputBlock
126 | {
127 | get
128 | {
129 | return this.m_parsingBlock;
130 | }
131 | }
132 |
133 | public override StatisticsRecorder Recorder
134 | {
135 | get
136 | {
137 | return this.m_recorder;
138 | }
139 | }
140 |
141 | #endregion
142 | }
143 | }
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestStatisticsRecorder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Microsoft.VisualStudio.TestTools.UnitTesting;
3 |
4 | namespace Gridsum.DataflowEx.Test
5 | {
6 | [TestClass]
7 | public class TestStatisticsRecorder
8 | {
9 | [TestMethod]
10 | public void TestMethod1()
11 | {
12 | var recorder = new StatisticsRecorder();
13 | recorder.Record("a");
14 | recorder.Record("a");
15 | recorder.Record("a");
16 | recorder.Record(1);
17 | recorder.Record(1);
18 | recorder.Record(1);
19 | recorder.Record(1);
20 | recorder.Record(new EventHolder());
21 | recorder.Record(new EventHolder());
22 | recorder.Record(new EventHolder());
23 | recorder.RecordEvent("ev1");
24 | recorder.RecordEvent("ev1");
25 | recorder.RecordEvent("ev1");
26 | recorder.RecordEvent("ev1");
27 | recorder.RecordEvent("ev1");
28 | recorder.RecordEvent("ev2", "1");
29 | recorder.RecordEvent("ev2", "2");
30 | recorder.RecordEvent("ev3", "1");
31 | recorder.RecordEvent("ev3", "1");
32 | recorder.RecordEvent("ev3", "1");
33 |
34 | Assert.AreEqual(3, recorder[typeof(string)]);
35 | Assert.AreEqual(3, recorder[typeof(EventHolder)]);
36 | Assert.AreEqual(4, recorder[typeof(int)]);
37 | Assert.AreEqual(5 + 3, recorder["ev1"]);
38 | Assert.AreEqual(2, recorder["ev2"]);
39 | Assert.AreEqual(3, recorder[new DataflowEvent("ev3", "1")]);
40 |
41 | Assert.AreEqual("a-b", new DataflowEvent("a", "b").ToString());
42 | Assert.AreEqual("a", new DataflowEvent("a").ToString());
43 | }
44 |
45 | internal class EventHolder : IEventProvider
46 | {
47 | public DataflowEvent GetEvent()
48 | {
49 | return new DataflowEvent("ev1");
50 | }
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestStringCondition.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Gridsum.DataflowEx.PatternMatch;
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 |
5 | namespace Gridsum.DataflowEx.Test
6 | {
7 | [TestClass]
8 | public class TestStringCondition
9 | {
10 | [TestMethod]
11 | public void TestStringCondition1()
12 | {
13 | var cond = new StringMatchCondition("abc");
14 | Assert.IsTrue(cond.Matches("abc"));
15 | Assert.IsFalse(cond.Matches("abc1"));
16 | }
17 |
18 | [TestMethod]
19 | public void TestStringCondition2()
20 | {
21 | var cond = new StringMatchCondition("abc", MatchType.BeginsWith);
22 | Assert.IsTrue(cond.Matches("abc"));
23 | Assert.IsTrue(cond.Matches("abc1"));
24 | Assert.IsFalse(cond.Matches("1abc"));
25 | }
26 |
27 | [TestMethod]
28 | public void TestStringCondition3()
29 | {
30 | var cond = new StringMatchCondition("abc", MatchType.Contains);
31 | Assert.IsTrue(cond.Matches("abc"));
32 | Assert.IsTrue(cond.Matches("abc1"));
33 | Assert.IsTrue(cond.Matches("1abc"));
34 | Assert.IsFalse(cond.Matches("abd"));
35 | }
36 |
37 | [TestMethod]
38 | public void TestStringCondition4()
39 | {
40 | var cond = new StringMatchCondition(".*ab[cd].*", MatchType.RegexMatch);
41 | Assert.IsTrue(cond.Matches("abc"));
42 | Assert.IsTrue(cond.Matches("abc1"));
43 | Assert.IsTrue(cond.Matches("1abc"));
44 | Assert.IsTrue(cond.Matches("abd"));
45 | Assert.IsFalse(cond.Matches("abef"));
46 | }
47 |
48 | [TestMethod]
49 | public void TestUrlStringMatchCondition()
50 | {
51 | var cond = new UrlStringMatchCondition(@"((http)|(https)://)?www.gridsum.com/.*\.txt$", MatchType.RegexMatch, true);
52 | Assert.IsTrue(cond.Matches("www.gridsum.com/1.txt?a=b"));
53 | Assert.IsTrue(cond.Matches("http://www.gridsum.com/2.txt#section2"));
54 | Assert.IsTrue(cond.Matches("https://www.gridsum.com/3.txt"));
55 | Assert.IsFalse(cond.Matches("https://www.gridsum.com/3.txta"));
56 | }
57 |
58 | [TestMethod]
59 | public void TestUrlStringMatchCondition2()
60 | {
61 | var cond = new UrlStringMatchCondition(@"((http)|(https)://)?www.gridsum.com/.*\.txt$", MatchType.RegexMatch, false);
62 | Assert.IsFalse(cond.Matches("www.gridsum.com/1.txt?a=b"));
63 | Assert.IsFalse(cond.Matches("http://www.gridsum.com/2.txt#section2"));
64 | Assert.IsTrue(cond.Matches("https://www.gridsum.com/3.txt"));
65 | Assert.IsFalse(cond.Matches("https://www.gridsum.com/3.txta"));
66 | }
67 |
68 | [TestMethod]
69 | public void TestExactMatch()
70 | {
71 | var cond1 = new StringMatchCondition("1", MatchType.Contains);
72 | var cond2 = new StringMatchCondition("2", MatchType.Contains);
73 | var cond3 = new StringMatchCondition("3", MatchType.Contains);
74 |
75 | var multi = new MultiMatchCondition(new[] { cond1, cond2, cond3 });
76 |
77 | Assert.IsTrue(multi.Matches("432"));
78 | Assert.IsTrue(multi.Matches("123"));
79 | Assert.AreEqual(cond2, multi.MatchesExact("432"));
80 | Assert.AreEqual(cond1, multi.MatchesExact("123"));
81 |
82 | }
83 |
84 | [TestMethod]
85 | public void TestExactMatch2()
86 | {
87 | var cond1 = new StringMatchable("1", MatchType.Contains);
88 | var cond2 = new StringMatchable("2", MatchType.Contains);
89 | var cond3 = new StringMatchable("3", MatchType.EndsWith);
90 |
91 | var multi = new MultiMatcher(new[] { cond1, cond2, cond3 });
92 |
93 | Assert.IsTrue(multi.Matches("432"));
94 | Assert.IsTrue(multi.Matches("123"));
95 | Assert.AreEqual(cond2.Condition, multi.MatchesExact("432"));
96 | Assert.AreEqual(cond1.Condition, multi.MatchesExact("123"));
97 |
98 | StringMatchable m;
99 | IMatchCondition c;
100 | Assert.IsTrue(multi.TryMatchExact("9873", out m, out c));
101 | Assert.AreEqual(cond3, m);
102 | Assert.AreEqual(cond3.Condition, c);
103 | }
104 |
105 | internal class StringMatchable : IMatchable
106 | {
107 | private readonly string m_pattern;
108 | private UrlStringMatchCondition m_cond;
109 |
110 | public StringMatchable(string pattern, MatchType matchType)
111 | {
112 | this.m_pattern = pattern;
113 | m_cond = new UrlStringMatchCondition(pattern, matchType, false);
114 | }
115 |
116 | public string Pattern
117 | {
118 | get
119 | {
120 | return m_pattern;
121 | }
122 | }
123 |
124 | public IMatchCondition Condition
125 | {
126 | get
127 | {
128 | return m_cond;
129 | }
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.Test/TestUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.Test
8 | {
9 | using DatabaseTests;
10 | using System.Data.SqlClient;
11 | using System.Diagnostics;
12 | using System.Reflection;
13 | using Microsoft.EntityFrameworkCore;
14 |
15 | public class DbContextBase : DbContext where T : DbContext
16 | {
17 | public DbContextBase(string connectString)
18 | : base(new DbContextOptionsBuilder().UseSqlServer(connectString).Options)
19 | {
20 | this.ConnectionString = connectString;
21 | this.Database.EnsureDeleted();
22 | this.Database.EnsureCreated();
23 | }
24 |
25 | public string ConnectionString { get; private set; }
26 |
27 |
28 | }
29 |
30 | public static class TestUtils
31 | {
32 | public static async Task FinishesIn(this Task t,TimeSpan ts)
33 | {
34 | var timeOutTask = Task.Delay(ts);
35 | return t == await Task.WhenAny(t, timeOutTask);
36 | }
37 |
38 | public static SqlConnection GetLocalDB(string dbName)
39 | {
40 | using (var conn = new SqlConnection(GetLocalDBConnectionString("master", false)))
41 | {
42 | conn.Open();
43 | var command = conn.CreateCommand();
44 | command.CommandText = $"IF db_id('DataflowEx-TestDB-{dbName}') is null BEGIN CREATE DATABASE [DataflowEx-TestDB-{dbName}] END;";
45 | command.ExecuteNonQuery();
46 | }
47 |
48 | var connToDBName = new SqlConnection(GetLocalDBConnectionString(dbName));
49 | connToDBName.Open();
50 | return connToDBName;
51 | }
52 |
53 | public static string GetLocalDBConnectionString(string dbName = null, bool addPrefix = true)
54 | {
55 | if (dbName == null)
56 | {
57 | var callingMethod = new StackTrace().GetFrame(1).GetMethod() as MethodInfo;
58 | dbName = callingMethod.DeclaringType.Name + "-" + callingMethod.Name;
59 | }
60 |
61 | SqlConnectionStringBuilder builder = new SqlConnectionStringBuilder();
62 | builder.DataSource = "localhost"; // update me
63 | builder.UserID = "sa"; // update me
64 | builder.Password = TestBootstrapper.s_saPassword;
65 | builder.InitialCatalog = addPrefix ? $"DataflowEx-TestDB-{dbName}" : dbName;
66 | return builder.ConnectionString;
67 | }
68 |
69 | public static byte[] ToByteArray(this string s)
70 | {
71 | return Encoding.UTF8.GetBytes(s);
72 | }
73 |
74 | public static bool ArrayEqual(byte[] a, byte[] b)
75 | {
76 | if (a.Length == b.Length)
77 | {
78 | for (int i = 0; i < a.Length; i++)
79 | {
80 | if (a[i] != b[i]) return false;
81 | }
82 |
83 | return true;
84 | }
85 | else
86 | {
87 | return false;
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.TestHelper/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.TestHelper/Gridsum.DataflowEx.TestHelper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.TestHelper/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.TestHelper
8 | {
9 | using Gridsum.DataflowEx.Test.ETL;
10 |
11 | class Program
12 | {
13 | static void Main(string[] args)
14 | {
15 | new TestDataJoiner().TestDataJoinerJoining().Wait();
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.30501.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gridsum.DataflowEx", "Gridsum.DataflowEx\Gridsum.DataflowEx.csproj", "{A4FD6288-AE2E-4C28-B7D4-D317D1601E86}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{77014771-4A9D-4A59-AB9D-B587668F5233}"
9 | ProjectSection(SolutionItems) = preProject
10 | .nuget\NuGet.Config = .nuget\NuGet.Config
11 | .nuget\NuGet.exe = .nuget\NuGet.exe
12 | .nuget\NuGet.targets = .nuget\NuGet.targets
13 | EndProjectSection
14 | EndProject
15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gridsum.DataflowEx.Test", "Gridsum.DataflowEx.Test\Gridsum.DataflowEx.Test.csproj", "{E5089AD2-0BE8-48E8-B361-A4C38B016C76}"
16 | EndProject
17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gridsum.DataflowEx.TestHelper", "Gridsum.DataflowEx.TestHelper\Gridsum.DataflowEx.TestHelper.csproj", "{69654373-6EC8-4005-8441-6F3B8368AEF8}"
18 | EndProject
19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gridsum.DataflowEx.Demo", "Gridsum.DataflowEx.Demo\Gridsum.DataflowEx.Demo.csproj", "{6DE0770B-8795-4F64-9AEE-4BD2550009FB}"
20 | EndProject
21 | Global
22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 | Debug|Any CPU = Debug|Any CPU
24 | Release|Any CPU = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {A4FD6288-AE2E-4C28-B7D4-D317D1601E86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {A4FD6288-AE2E-4C28-B7D4-D317D1601E86}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {A4FD6288-AE2E-4C28-B7D4-D317D1601E86}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {A4FD6288-AE2E-4C28-B7D4-D317D1601E86}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {E5089AD2-0BE8-48E8-B361-A4C38B016C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {E5089AD2-0BE8-48E8-B361-A4C38B016C76}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {E5089AD2-0BE8-48E8-B361-A4C38B016C76}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {E5089AD2-0BE8-48E8-B361-A4C38B016C76}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {69654373-6EC8-4005-8441-6F3B8368AEF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {69654373-6EC8-4005-8441-6F3B8368AEF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {69654373-6EC8-4005-8441-6F3B8368AEF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {69654373-6EC8-4005-8441-6F3B8368AEF8}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {6DE0770B-8795-4F64-9AEE-4BD2550009FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {6DE0770B-8795-4F64-9AEE-4BD2550009FB}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {6DE0770B-8795-4F64-9AEE-4BD2550009FB}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {6DE0770B-8795-4F64-9AEE-4BD2550009FB}.Release|Any CPU.Build.0 = Release|Any CPU
43 | EndGlobalSection
44 | GlobalSection(SolutionProperties) = preSolution
45 | HideSolutionNode = FALSE
46 | EndGlobalSection
47 | EndGlobal
48 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/AutoCompleteWrapper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Timers;
6 | using System.Threading.Tasks;
7 | using System.Threading.Tasks.Dataflow;
8 |
9 | namespace Gridsum.DataflowEx.AutoCompletion
10 | {
11 | public class AutoCompleteWrapper : Dataflow
12 | where TIn : ITracableItem
13 | where TOut : ITracableItem
14 | {
15 | private readonly TimeSpan m_processTimeout;
16 | private readonly Timer m_timer;
17 | private Guid? m_last;
18 | private Dataflow m_before;
19 | private Dataflow m_after;
20 | private Dataflow m_Dataflow;
21 |
22 | public AutoCompleteWrapper(Dataflow dataflow, TimeSpan processTimeout, DataflowOptions options) : base(options)
23 | {
24 | m_Dataflow = dataflow;
25 | m_processTimeout = processTimeout;
26 | m_timer = new Timer();
27 | m_timer.Interval = m_processTimeout.TotalMilliseconds;
28 | m_timer.Elapsed += OnTimerElapsed;
29 |
30 | var before = new TransformBlock(@in =>
31 | {
32 | if (m_last == null || @in.UniqueId == m_last.Value)
33 | {
34 | //The last one is back, so there is nothing else in the pipeline.
35 | //Set a timer: if nothing new produced when timer expires, the whole loop ends.
36 | m_timer.Start();
37 | }
38 | return @in;
39 | });
40 |
41 | m_before = DataflowUtils.FromBlock(before);
42 |
43 | var after = new TransformBlock(@out =>
44 | {
45 | if (@out.UniqueId != Guid.Empty)
46 | {
47 | m_last = @out.UniqueId;
48 | m_timer.Stop();
49 | }
50 | else
51 | {
52 | LogHelper.Logger.WarnFormat("Empty guid found in output. You may have forgotten to set it.");
53 | }
54 |
55 | return @out;
56 | });
57 |
58 | m_after = DataflowUtils.FromBlock(after);
59 |
60 | m_before.GoTo(dataflow).GoTo(m_after);
61 |
62 | RegisterChild(m_before);
63 | RegisterChild(dataflow);
64 | RegisterChild(m_after);
65 | }
66 |
67 | void OnTimerElapsed(object sender, ElapsedEventArgs e)
68 | {
69 | LogHelper.Logger.InfoFormat("Auto complete timer elapsed. Shutting down the inner dataflow ({0})..", m_Dataflow.FullName);
70 |
71 | m_before.Complete(); //pass completion down to the chain
72 | }
73 |
74 | public override ISourceBlock OutputBlock
75 | {
76 | get { return m_after.OutputBlock; }
77 | }
78 |
79 | public override ITargetBlock InputBlock
80 | {
81 | get { return m_before.InputBlock; }
82 | }
83 |
84 | public override string Name
85 | {
86 | get
87 | {
88 | return string.Format("{0}-AutoComplete", m_Dataflow.Name);
89 | }
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/ControlItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.AutoCompletion
8 | {
9 | public class ControlItem : ITracableItem
10 | {
11 | public Guid UniqueId
12 | {
13 | get;
14 | set;
15 | }
16 |
17 | public ControlType Type { get; set; }
18 | }
19 |
20 | //todo: ISpiderComponent1Item -> SpiderRequest, Control
21 | //todo: ISpiderComponent2Item -> SpiderResponse, Control
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/ControlType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 |
6 | namespace Gridsum.DataflowEx.AutoCompletion
7 | {
8 | public enum ControlType
9 | {
10 | BeforeLast,
11 | AfterLast
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/HeartbeatNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Gridsum.DataflowEx;
7 | using System.Threading.Tasks.Dataflow;
8 |
9 | namespace Gridsum.DataflowEx.AutoCompletion
10 | {
11 | ///
12 | /// A dataflow node used in ring completion detection infrastructure.
13 | ///
14 | public interface IHeartbeatNode : IRingNode
15 | {
16 | long ProcessedItemCount { get; }
17 |
18 | bool NoHeartbeatDuring(Action action);
19 | Task NoHeartbeatDuring(Func action);
20 | }
21 |
22 | ///
23 | /// A default implementation of IHeartbeatNode, used in ring completion detection.
24 | ///
25 | public class HeartbeatNode : Dataflow, IHeartbeatNode
26 | {
27 | private long m_beats;
28 | private TransformBlock m_block;
29 |
30 | public HeartbeatNode(DataflowOptions options) : base(options)
31 | {
32 | m_beats = 0;
33 |
34 | Func f = arg =>
35 | {
36 | IsBusy = true;
37 | m_beats++;
38 | IsBusy = false;
39 | return arg;
40 | };
41 |
42 | m_block = new TransformBlock(f, options.ToExecutionBlockOption());
43 | RegisterChild(m_block);
44 | }
45 |
46 | public override ITargetBlock InputBlock
47 | {
48 | get
49 | {
50 | return m_block;
51 | }
52 | }
53 |
54 | public override ISourceBlock OutputBlock
55 | {
56 | get
57 | {
58 | return m_block;
59 | }
60 | }
61 |
62 | public long ProcessedItemCount
63 | {
64 | get
65 | {
66 | return m_beats;
67 | }
68 | }
69 |
70 | public bool NoHeartbeatDuring(Action action)
71 | {
72 | long before = m_beats;
73 | action();
74 | long after = m_beats;
75 | return after == before;
76 | }
77 |
78 | public async Task NoHeartbeatDuring(Func action)
79 | {
80 | long before = m_beats;
81 | await action().ConfigureAwait(false);
82 | long after = m_beats;
83 | return after == before;
84 | }
85 |
86 | public bool IsBusy { get; private set; }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/IRingNode.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.AutoCompletion
8 | {
9 | public interface IRingNode : IDataflow
10 | {
11 | bool IsBusy { get; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/ITracableItem.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.AutoCompletion
8 | {
9 | public interface ITracableItem
10 | {
11 | Guid UniqueId { get; }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/RingMonitor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx.AutoCompletion
8 | {
9 | public class RingMonitor
10 | {
11 | private readonly Dataflow m_host;
12 | private readonly IRingNode[] m_ringNodes;
13 | private readonly Lazy m_lazyDisplayName;
14 | private IHeartbeatNode m_hb;
15 |
16 | public RingMonitor(Dataflow host, IRingNode[] ringNodes)
17 | {
18 | if (ringNodes.Length == 0)
19 | {
20 | throw new ArgumentException("The child ring contains nothing");
21 | }
22 |
23 | m_hb = ringNodes.OfType().FirstOrDefault();
24 | if (m_hb == null)
25 | {
26 | throw new ArgumentException("A ring must contain at least one IHeartbeatNode");
27 | }
28 |
29 | var nonChild = ringNodes.FirstOrDefault(r => { return !host.IsMyChild(r); });
30 | if (nonChild != null)
31 | {
32 | throw new ArgumentException("The child ring contains a non-child: " + nonChild.FullName);
33 | }
34 |
35 | this.m_host = host;
36 | this.m_ringNodes = ringNodes;
37 |
38 | m_lazyDisplayName = new Lazy(this.GetDisplayName);
39 | }
40 |
41 | private string GetDisplayName()
42 | {
43 | string ringString = string.Join("=>", m_ringNodes.Select(n => n.Name));
44 | return string.Format("({0})", ringString);
45 | }
46 |
47 | public string DisplayName
48 | {
49 | get
50 | {
51 | return m_lazyDisplayName.Value;
52 | }
53 | }
54 |
55 | public async Task StartMonitoring(Task preTask)
56 | {
57 | LogHelper.Logger.InfoFormat("{0} A ring is set up: {1}", m_host.FullName, DisplayName);
58 | await preTask.ConfigureAwait(false);
59 | LogHelper.Logger.InfoFormat("{0} Ring pretask done. Starting check loop for {1}", m_host.FullName, DisplayName);
60 |
61 | while (true)
62 | {
63 | await Task.Delay(m_host.m_dataflowOptions.MonitorInterval).ConfigureAwait(false);
64 |
65 | bool empty = false;
66 |
67 | if (m_hb.NoHeartbeatDuring(() => { empty = this.IsRingEmpty(); }) && empty)
68 | {
69 | LogHelper.Logger.DebugFormat("{0} 1st level empty ring check passed for {1}", m_host.FullName, this.DisplayName);
70 |
71 | bool noHeartbeat = await m_hb.NoHeartbeatDuring(
72 | async () =>
73 | {
74 | //trigger batch
75 | foreach (var batchedFlow in m_ringNodes.OfType())
76 | {
77 | batchedFlow.TriggerBatch();
78 | }
79 |
80 | await Task.Delay(m_host.m_dataflowOptions.MonitorInterval).ConfigureAwait(false);
81 |
82 | empty = this.IsRingEmpty();
83 | }).ConfigureAwait(false);
84 |
85 | if (noHeartbeat && empty)
86 | {
87 | LogHelper.Logger.DebugFormat("{0} 2nd level empty ring check passed for {1}", m_host.FullName, this.DisplayName);
88 |
89 | m_hb.Complete(); //start the completion domino :)
90 | break;
91 | }
92 | else
93 | {
94 | //batched flow dumped some hidden elements to the flow, go back to the loop
95 | LogHelper.Logger.DebugFormat("{0} New elements entered the ring by batched flows ({1}). Will fall back to 1st level empty ring check.", m_host.FullName, this.DisplayName);
96 | }
97 | }
98 | }
99 |
100 | LogHelper.Logger.InfoFormat("{0} Ring completion detected! Completion triggered on heartbeat node: {1}", m_host.FullName, DisplayName);
101 | }
102 |
103 | private bool IsRingEmpty()
104 | {
105 | var hb = m_ringNodes.OfType().First();
106 |
107 | if (m_ringNodes.All(r => !r.IsBusy))
108 | {
109 | long before = hb.ProcessedItemCount;
110 | int buffered = m_ringNodes.Sum(r => r.BufferedCount) + m_ringNodes[0].BufferedCount;
111 | long after = hb.ProcessedItemCount;
112 |
113 | if (buffered == 0 && before == after)
114 | {
115 | return true;
116 | }
117 | }
118 |
119 | return false;
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/AutoCompletion/TracableItemBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Gridsum.DataflowEx.AutoCompletion
4 | {
5 | public class TracableItemBase : ITracableItem
6 | {
7 | public TracableItemBase() : this(Guid.NewGuid())
8 | {
9 | }
10 |
11 | public TracableItemBase(Guid guid)
12 | {
13 | this.UniqueId = guid;
14 | }
15 |
16 | public Guid UniqueId { get; private set; }
17 | }
18 | }
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/Blocks/TransformAndLinkPropagator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Threading.Tasks.Dataflow;
7 |
8 | namespace Gridsum.DataflowEx.Blocks
9 | {
10 | internal sealed class TransformAndLinkPropagator : IPropagatorBlock, ITargetBlock, ISourceBlock, IDataflowBlock
11 | {
12 | private readonly ISourceBlock m_source;
13 | private readonly ITargetBlock m_target;
14 | private readonly Predicate m_userProvidedPredicate;
15 | private readonly Func m_transform;
16 |
17 | Task IDataflowBlock.Completion
18 | {
19 | get
20 | {
21 | return this.m_source.Completion;
22 | }
23 | }
24 |
25 | internal TransformAndLinkPropagator(ISourceBlock source, ITargetBlock target, Predicate predicate, Func transform)
26 | {
27 | this.m_source = source;
28 | this.m_target = target;
29 | this.m_transform = transform;
30 | this.m_userProvidedPredicate = predicate;
31 | }
32 |
33 | private bool RunPredicate(TIn item)
34 | {
35 | return this.m_userProvidedPredicate(item);
36 | }
37 |
38 | DataflowMessageStatus ITargetBlock.OfferMessage(DataflowMessageHeader messageHeader, TIn messageValue, ISourceBlock source, bool consumeToAccept)
39 | {
40 | if (!messageHeader.IsValid)
41 | throw new ArgumentException("message header is invalid", "messageHeader");
42 | if (source == null)
43 | throw new ArgumentNullException("source");
44 | if (this.RunPredicate(messageValue))
45 | return this.m_target.OfferMessage(messageHeader, m_transform(messageValue), (ISourceBlock)this, consumeToAccept);
46 | else
47 | return DataflowMessageStatus.Declined;
48 | }
49 |
50 | TOut ISourceBlock.ConsumeMessage(DataflowMessageHeader messageHeader, ITargetBlock target, out bool messageConsumed)
51 | {
52 | TIn raw = this.m_source.ConsumeMessage(messageHeader, (ITargetBlock)this, out messageConsumed);
53 | return m_transform(raw);
54 | }
55 |
56 | bool ISourceBlock.ReserveMessage(DataflowMessageHeader messageHeader, ITargetBlock target)
57 | {
58 | return this.m_source.ReserveMessage(messageHeader, (ITargetBlock)this);
59 | }
60 |
61 | void ISourceBlock.ReleaseReservation(DataflowMessageHeader messageHeader, ITargetBlock target)
62 | {
63 | this.m_source.ReleaseReservation(messageHeader, (ITargetBlock)this);
64 | }
65 |
66 | void IDataflowBlock.Complete()
67 | {
68 | this.m_target.Complete();
69 | }
70 |
71 | void IDataflowBlock.Fault(Exception exception)
72 | {
73 | this.m_target.Fault(exception);
74 | }
75 |
76 | IDisposable ISourceBlock.LinkTo(ITargetBlock target, DataflowLinkOptions linkOptions)
77 | {
78 | throw new NotSupportedException();
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/DataBroadcaster.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Threading.Tasks.Dataflow;
7 |
8 | namespace Gridsum.DataflowEx
9 | {
10 | using System.Collections.Immutable;
11 |
12 | ///
13 | /// BroadcastBlock in TPL Dataflow only pushes latest data (if destination is full) and causes data loss.
14 | /// That's why we need this DataBroadcaster which preserves a 100% same copy of the data stream
15 | ///
16 | /// The input and output type of the data flow
17 | public class DataBroadcaster : Dataflow
18 | {
19 | //todo: fix race condition
20 | private ImmutableList> m_copyBuffers;
21 |
22 | private readonly TransformBlock m_transformBlock;
23 |
24 | ///
25 | /// Construct an DataBroadcaster instance
26 | ///
27 | public DataBroadcaster() : this(DataflowOptions.Default) {}
28 |
29 | ///
30 | /// Construct an DataBroadcaster instance
31 | ///
32 | /// the option of this dataflow
33 | public DataBroadcaster(DataflowOptions dataflowOptions) : this(null, dataflowOptions) {}
34 |
35 | ///
36 | /// Construct an DataBroadcaster instance
37 | ///
38 | /// The copy function when broadcasting
39 | /// the option of this dataflow
40 | public DataBroadcaster(Func copyFunc, DataflowOptions dataflowOptions) : base(dataflowOptions)
41 | {
42 | m_copyBuffers = ImmutableList>.Empty;
43 |
44 | m_transformBlock = new TransformBlock(
45 | async arg =>
46 | {
47 | T copy = copyFunc == null ? arg : copyFunc(arg);
48 | foreach (var buffer in m_copyBuffers)
49 | {
50 | await buffer.SendAsync(copy).ConfigureAwait(false);
51 | }
52 | return arg;
53 | }, dataflowOptions.ToExecutionBlockOption());
54 |
55 | RegisterChild(m_transformBlock);
56 | }
57 |
58 | ///
59 | /// See
60 | ///
61 | public override ITargetBlock InputBlock
62 | {
63 | get { return m_transformBlock; }
64 | }
65 |
66 | ///
67 | /// See
68 | ///
69 | public override ISourceBlock OutputBlock
70 | {
71 | get { return m_transformBlock; }
72 | }
73 |
74 | ///
75 | /// Link the copied data stream to another block
76 | ///
77 | private void LinkCopyTo(IDataflow other)
78 | {
79 | //first, create a new copy block
80 | Dataflow copyBuffer = new BufferBlock(m_dataflowOptions.ToGroupingBlockOption()).ToDataflow(m_dataflowOptions);
81 |
82 | RegisterChild(copyBuffer);
83 | copyBuffer.RegisterDependency(m_transformBlock);
84 |
85 | var afterAdd = ImmutableUtils.AddOptimistically(ref m_copyBuffers, copyBuffer);
86 |
87 | copyBuffer.Name = "Buffer" + afterAdd.Count;
88 | copyBuffer.LinkTo(other);
89 | }
90 |
91 | ///
92 | /// See
93 | ///
94 | public override IDataflow GoTo(IDataflow other, Predicate predicate)
95 | {
96 | if (predicate != null)
97 | {
98 | throw new ArgumentException("DataBroadcaster does not support predicate linking", "predicate");
99 | }
100 |
101 | if (m_condBuilder.Count == 0) //not linked to any target yet
102 | {
103 | //link first output as primary output
104 | base.GoTo(other);
105 | }
106 | else
107 | {
108 | this.LinkCopyTo(other);
109 | }
110 |
111 | LogHelper.Logger.InfoFormat("{0} now links to its {1}th target ({2})", this.FullName, m_copyBuffers.Count + 1, other.Name);
112 | return other;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/Gridsum.DataflowEx/DataDispatcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Gridsum.DataflowEx
8 | {
9 | using System.Collections.Concurrent;
10 | using System.Collections.Immutable;
11 | using System.Threading.Tasks.Dataflow;
12 |
13 | ///
14 | /// Provides an abstract flow that dispatch inputs to multiple child flows by a special dispatch function, which is
15 | /// useful in situations that you want to group inputs by a certain property and let specific child flows to take
16 | /// care of different groups independently. DataDispatcher also helps creating and maintaining child flows dynamically
17 | /// in a thread-safe way.
18 | ///
19 | /// Type of input items of this dispatcher flow
20 | /// Type of the dispatch key to group input items
21 | ///
22 | /// This flow guarantees an input goes to only ONE of the child flows. Notice the difference comparing to DataBroadcaster, which
23 | /// gives the input to EVERY flow it is linked to.
24 | ///
25 | public abstract class DataDispatcher : Dataflow
26 | {
27 | protected ActionBlock m_dispatcherBlock;
28 | protected ConcurrentDictionary>> m_destinations;
29 | private Func>> m_initer;
30 |
31 | ///
32 | /// Construct an DataDispatcher instance
33 | ///
34 | /// The dispatch function
35 | public DataDispatcher(Func dispatchFunc) : this(dispatchFunc, DataflowOptions.Default)
36 | {
37 | }
38 |
39 | ///
40 | /// Construct an DataDispatcher instance
41 | ///
42 | /// The dispatch function
43 | /// Option for this dataflow
44 | public DataDispatcher(Func dispatchFunc, DataflowOptions option)
45 | : this(dispatchFunc, EqualityComparer.Default, option)
46 | {
47 | }
48 |
49 | ///
50 | /// Construct an DataDispatcher instance
51 | ///
52 | /// The dispatch function
53 | /// The key comparer for this dataflow
54 | /// Option for this dataflow
55 | public DataDispatcher(Func dispatchFunc, EqualityComparer keyComparer, DataflowOptions option)
56 | : base(option)
57 | {
58 | m_destinations = new ConcurrentDictionary>>(keyComparer);
59 |
60 | m_initer = key => new Lazy>(
61 | () =>
62 | {
63 | var child = this.CreateChildFlow(key);
64 | RegisterChild(child);
65 | child.RegisterDependency(m_dispatcherBlock);
66 | return child;
67 | });
68 |
69 | m_dispatcherBlock = new ActionBlock(async
70 | input =>
71 | {
72 | var childFlow = m_destinations.GetOrAdd(dispatchFunc(input), m_initer).Value;
73 | await childFlow.SendAsync(input).ConfigureAwait(false);
74 | }, option.ToExecutionBlockOption());
75 |
76 | RegisterChild(m_dispatcherBlock);
77 | }
78 |
79 | ///
80 | /// Create the child flow on-the-fly by the dispatch key
81 | ///
82 | /// The unique key to create and identify the child flow
83 | /// A new child dataflow which is responsible for processing items having the given dispatch key
84 | ///
85 | /// The dispatch key should have a one-one relatioship with child flow
86 | ///
87 | protected abstract Dataflow CreateChildFlow(TKey dispatchKey);
88 |
89 | ///
90 | /// See