Skip to content

Commit 3321dfd

Browse files
committed
Init
1 parent 02c61f0 commit 3321dfd

26 files changed

+1944
-0
lines changed

.gitattributes

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
###############################################################################
2+
# Set default behavior to automatically normalize line endings.
3+
###############################################################################
4+
* text=auto
5+
6+
###############################################################################
7+
# Set default behavior for command prompt diff.
8+
#
9+
# This is need for earlier builds of msysgit that does not have it on by
10+
# default for csharp files.
11+
# Note: This is only used by command line
12+
###############################################################################
13+
#*.cs diff=csharp
14+
15+
###############################################################################
16+
# Set the merge driver for project and solution files
17+
#
18+
# Merging from the command prompt will add diff markers to the files if there
19+
# are conflicts (Merging from VS is not affected by the settings below, in VS
20+
# the diff markers are never inserted). Diff markers may cause the following
21+
# file extensions to fail to load in VS. An alternative would be to treat
22+
# these files as binary and thus will always conflict and require user
23+
# intervention with every merge. To do so, just uncomment the entries below
24+
###############################################################################
25+
#*.sln merge=binary
26+
#*.csproj merge=binary
27+
#*.vbproj merge=binary
28+
#*.vcxproj merge=binary
29+
#*.vcproj merge=binary
30+
#*.dbproj merge=binary
31+
#*.fsproj merge=binary
32+
#*.lsproj merge=binary
33+
#*.wixproj merge=binary
34+
#*.modelproj merge=binary
35+
#*.sqlproj merge=binary
36+
#*.wwaproj merge=binary
37+
38+
###############################################################################
39+
# behavior for image files
40+
#
41+
# image files are treated as binary by default.
42+
###############################################################################
43+
#*.jpg binary
44+
#*.png binary
45+
#*.gif binary
46+
47+
###############################################################################
48+
# diff behavior for common document formats
49+
#
50+
# Convert binary document formats to text before diffing them. This feature
51+
# is only available from the command line. Turn it on by uncommenting the
52+
# entries below.
53+
###############################################################################
54+
#*.doc diff=astextplain
55+
#*.DOC diff=astextplain
56+
#*.docx diff=astextplain
57+
#*.DOCX diff=astextplain
58+
#*.dot diff=astextplain
59+
#*.DOT diff=astextplain
60+
#*.pdf diff=astextplain
61+
#*.PDF diff=astextplain
62+
#*.rtf diff=astextplain
63+
#*.RTF diff=astextplain

.github/workflows/dotnet.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: .NET
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
pull_request:
7+
branches: [ main ]
8+
9+
jobs:
10+
build:
11+
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Setup .NET
17+
uses: actions/setup-dotnet@v1
18+
with:
19+
dotnet-version: 5.0.x
20+
- name: Restore dependencies
21+
run: dotnet restore
22+
- name: Build
23+
run: dotnet build --no-restore
24+
- name: Test
25+
run: dotnet test --no-build --verbosity normal

.gitignore

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
## Ignore Visual Studio temporary files, build results, and
2+
## files generated by popular Visual Studio add-ons.
3+
##
4+
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5+
6+
# User-specific files
7+
*.rsuser
8+
*.suo
9+
*.user
10+
*.userosscache
11+
*.sln.docstates
12+
13+
# User-specific files (MonoDevelop/Xamarin Studio)
14+
*.userprefs
15+
16+
# Build results
17+
[Dd]ebug/
18+
[Dd]ebugPublic/
19+
[Rr]elease/
20+
[Rr]eleases/
21+
x64/
22+
x86/
23+
[Aa][Rr][Mm]/
24+
[Aa][Rr][Mm]64/
25+
bld/
26+
[Bb]in/
27+
[Oo]bj/
28+
[Ll]og/
29+
30+
# .NET Core
31+
project.lock.json
32+
project.fragment.lock.json
33+
artifacts/
34+
35+
# StyleCop
36+
StyleCopReport.xml
37+
38+
# Files built by Visual Studio
39+
*_i.c
40+
*_p.c
41+
*_h.h
42+
*.ilk
43+
*.meta
44+
*.obj
45+
*.iobj
46+
*.pch
47+
*.pdb
48+
*.ipdb
49+
*.pgc
50+
*.pgd
51+
*.rsp
52+
*.sbr
53+
*.tlb
54+
*.tli
55+
*.tlh
56+
*.tmp
57+
*.tmp_proj
58+
*_wpftmp.csproj
59+
*.log
60+
*.vspscc
61+
*.vssscc
62+
.builds
63+
*.pidb
64+
*.svclog
65+
*.scc
66+
67+
# ReSharper is a .NET coding add-in
68+
_ReSharper*/
69+
*.[Rr]e[Ss]harper
70+
*.DotSettings.user
71+
72+
# Visual Studio code coverage results
73+
*.coverage
74+
*.coveragexml
75+
76+
# Click-Once directory
77+
publish/
78+
79+
# Publish Web Output
80+
*.[Pp]ublish.xml
81+
*.azurePubxml
82+
# Note: Comment the next line if you want to checkin your web deploy settings,
83+
# but database connection strings (with potential passwords) will be unencrypted
84+
*.pubxml
85+
*.publishproj
86+
87+
# Microsoft Azure Web App publish settings. Comment the next line if you want to
88+
# checkin your Azure Web App publish settings, but sensitive information contained
89+
# in these scripts will be unencrypted
90+
PublishScripts/
91+
92+
# NuGet Packages
93+
*.nupkg
94+
# The packages folder can be ignored because of Package Restore
95+
**/[Pp]ackages/*
96+
# except build/, which is used as an MSBuild target.
97+
!**/[Pp]ackages/build/
98+
# Uncomment if necessary however generally it will be regenerated when needed
99+
#!**/[Pp]ackages/repositories.config
100+
# NuGet v3's project.json files produces more ignorable files
101+
*.nuget.props
102+
*.nuget.targets
103+
104+
# Visual Studio cache files
105+
# files ending in .cache can be ignored
106+
*.[Cc]ache
107+
# but keep track of directories ending in .cache
108+
!?*.[Cc]ache/
109+
110+
# Others
111+
ClientBin/
112+
~$*
113+
*~
114+
*.dbmdl
115+
*.dbproj.schemaview
116+
*.jfm
117+
*.pfx
118+
*.publishsettings
119+
orleans.codegen.cs
120+
121+
# Including strong name files can present a security risk
122+
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
123+
#*.snk
124+
125+
# SQL Server files
126+
*.mdf
127+
*.ldf
128+
*.ndf
129+
130+
# MSBuild Binary and Structured Log
131+
*.binlog
132+
133+
# Local History for Visual Studio
134+
.localhistory/

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# SQL Bulk Copy & Merge
2+
3+
SQLBulkCopy is useful to copy between databases, but truncating the destination table each time before copying is not always possible or efficient.
4+
5+
To solve this problem, this .NET library has the following methods:
6+
7+
### CopyAndMerge
8+
Uses SQLBulkCopy to copy data from a table or view in the source database to a temporary table in the target database before running SQL MERGE from the temporary table to the destination table.
9+
10+
The specific steps it performs:
11+
- Checks target schema and table exists. If not, creates them
12+
- Create temp table (target table name appended with '_temp'
13+
- Generate MERGE statement with the temp table as source and targetTable as target
14+
- Copy from table in source db to temp table in target db
15+
- Run MERGE statement
16+
- Drop the temp table
17+
18+
Usage:
19+
```
20+
var copyService = new SqlBulkCopyMergeService(sourceDbConnectionString, targetDbConnectionString);
21+
var result = await copyService.CopyAndMerge(sourceTable, targetTable);
22+
Console.WriteLine("Rows Inserted: " + result.Inserted);
23+
Console.WriteLine("Rows Updated: " + result.Updated);
24+
Console.WriteLine("Rows Deleted: " + result.Deleted);
25+
```
26+
27+
### CopyLatest
28+
For source tables that are only ever added to it is more efficient to copy only the new rows into the target table.
29+
This method copies the latest data determined by the keyColumnName.
30+
31+
A query is made on the target for the latest keyColumnName value, and then source is queried where the keyColumnName value is greater than the latest in the target.
32+
Eg. if the keyColumnName is [id], and its latest value is 2, then only rows with an [id] value greater than 2 are copied.
33+
34+
If no keyColumnName is specified, the primary key is used. If more than one primary key, the first is used.
35+
36+
The result of the source query is directly copied into the target table using SQLBulkCopy.
37+
38+
Usage:
39+
```
40+
var copyService = new SqlBulkCopyMergeService(sourceDbConnectionString, targetDbConnectionString);
41+
var result = await copyService.CopyLatest(sourceTable, targetTable, keyColumnName);
42+
Console.WriteLine("Rows Copied: " + result.RowsCopied);
43+
```
44+
45+
### Notes
46+
For the connections, the Source database requires READER permission, the Target database requires READER + WRITER + CREATE TABLE + EXECUTE permissions.
47+
48+
Tested on SQL Server 2019.
49+
50+
Spatial types (Geometry, Geography) are unsupported because SQLBulkCopy does not support them.

SqlBulkCopyMerge.sln

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.31129.286
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlBulkCopyMerge", "src\SqlBulkCopyMerge\SqlBulkCopyMerge.csproj", "{8CF3861C-E4C3-4F31-B99D-AED162FA7D74}"
7+
EndProject
8+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlBulkCopyMerge.Tests", "test\SqlBulkCopyMerge.Tests\SqlBulkCopyMerge.Tests.csproj", "{B2FC446A-387A-41D7-B385-68CD00BC9B68}"
9+
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0C538267-AC95-40FA-B1F1-5B7874F8E102}"
11+
EndProject
12+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D15811D-BE33-486A-BE7F-5C0790841766}"
13+
EndProject
14+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE7E6138-BA06-4B5A-81CB-7332190C1AAC}"
15+
ProjectSection(SolutionItems) = preProject
16+
.github\workflows\dotnet.yml = .github\workflows\dotnet.yml
17+
README.md = README.md
18+
EndProjectSection
19+
EndProject
20+
Global
21+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
22+
Debug|Any CPU = Debug|Any CPU
23+
Release|Any CPU = Release|Any CPU
24+
EndGlobalSection
25+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
26+
{8CF3861C-E4C3-4F31-B99D-AED162FA7D74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{8CF3861C-E4C3-4F31-B99D-AED162FA7D74}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{8CF3861C-E4C3-4F31-B99D-AED162FA7D74}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{8CF3861C-E4C3-4F31-B99D-AED162FA7D74}.Release|Any CPU.Build.0 = Release|Any CPU
30+
{B2FC446A-387A-41D7-B385-68CD00BC9B68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31+
{B2FC446A-387A-41D7-B385-68CD00BC9B68}.Debug|Any CPU.Build.0 = Debug|Any CPU
32+
{B2FC446A-387A-41D7-B385-68CD00BC9B68}.Release|Any CPU.ActiveCfg = Release|Any CPU
33+
{B2FC446A-387A-41D7-B385-68CD00BC9B68}.Release|Any CPU.Build.0 = Release|Any CPU
34+
EndGlobalSection
35+
GlobalSection(SolutionProperties) = preSolution
36+
HideSolutionNode = FALSE
37+
EndGlobalSection
38+
GlobalSection(NestedProjects) = preSolution
39+
{8CF3861C-E4C3-4F31-B99D-AED162FA7D74} = {0C538267-AC95-40FA-B1F1-5B7874F8E102}
40+
{B2FC446A-387A-41D7-B385-68CD00BC9B68} = {0D15811D-BE33-486A-BE7F-5C0790841766}
41+
EndGlobalSection
42+
GlobalSection(ExtensibilityGlobals) = postSolution
43+
SolutionGuid = {50176CAA-A58B-49DE-BAAC-63D0976CEF0F}
44+
EndGlobalSection
45+
EndGlobal

src/SqlBulkCopyMerge/Logger.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
3+
namespace SqlBulkCopyMerge
4+
{
5+
public interface ILogger
6+
{
7+
public void Verbose(string message, params object[] args);
8+
public void Debug(string message, params object[] args);
9+
public void Warning(string message, params object[] args);
10+
public void Warning(Exception ex, string message, params object[] args);
11+
public void Error(string message, params object[] args);
12+
public void Error(Exception ex, string message, params object[] args);
13+
}
14+
15+
public static class Log
16+
{
17+
public static ILogger Logger { get; set; } = new EmptyLogger();
18+
19+
public static void Verbose(string message, params object[] args)
20+
{
21+
Logger?.Verbose(message, args);
22+
}
23+
24+
public static void Debug(string message, params object[] args)
25+
{
26+
Logger?.Debug(message, args);
27+
}
28+
29+
public static void Warning(string message, params object[] args)
30+
{
31+
Logger?.Warning(message, args);
32+
}
33+
34+
public static void Error(string message, params object[] args)
35+
{
36+
Logger?.Error(message, args);
37+
}
38+
}
39+
40+
public class EmptyLogger : ILogger
41+
{
42+
public void Verbose(string message, params object[] args)
43+
{
44+
}
45+
46+
public void Debug(string message, params object[] args)
47+
{
48+
}
49+
50+
public void Warning(string message, params object[] args)
51+
{
52+
}
53+
54+
public void Warning(Exception ex, string message, params object[] args)
55+
{
56+
}
57+
58+
public void Error(string message, params object[] args)
59+
{
60+
}
61+
62+
public void Error(Exception ex, string message, params object[] args)
63+
{
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)