Skip to content

Commit 5fc18df

Browse files
committed
more data merge fixes
1 parent 7686bcf commit 5fc18df

10 files changed

+381
-70
lines changed

src/FluentCommand.SqlServer/Merge/DataMergeColumn.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,21 @@ public DataMergeColumn()
7272
public bool IsKey { get; set; }
7373

7474
/// <summary>
75-
/// Gets or sets a value indicating whether the column is ignored, not used by merge.
75+
/// Gets or sets a value indicating whether the column is ignored, not used by merge.
7676
/// </summary>
7777
/// <value>
7878
/// <c>true</c> if the column is ignored; otherwise, <c>false</c>.
7979
/// </value>
8080
public bool IsIgnored { get; set; }
81+
82+
/// <summary>
83+
/// Converts to string.
84+
/// </summary>
85+
/// <returns>
86+
/// A <see cref="System.String" /> that represents this instance.
87+
/// </returns>
88+
public override string ToString()
89+
{
90+
return $"Source: {SourceColumn}, Target: {TargetColumn}, NativeType: {NativeType}, Key: {IsKey}, Ignored: {IsIgnored}";
91+
}
8192
}

src/FluentCommand.SqlServer/Merge/DataMergeDefinition.cs

Lines changed: 14 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using System.ComponentModel;
2-
using System.ComponentModel.DataAnnotations;
3-
using System.ComponentModel.DataAnnotations.Schema;
4-
51
using FluentCommand.Extensions;
2+
using FluentCommand.Reflection;
63

74
namespace FluentCommand.Merge;
85

@@ -116,64 +113,29 @@ public static DataMergeDefinition Create<TEntity>()
116113
/// <param name="mergeDefinition">The merge definition up auto map to.</param>
117114
public static void AutoMap<TEntity>(DataMergeDefinition mergeDefinition)
118115
{
119-
var entityType = typeof(TEntity);
120-
var properties = TypeDescriptor.GetProperties(entityType);
121-
122-
123-
var tableAttribute = Attribute.GetCustomAttribute(entityType, typeof(TableAttribute)) as TableAttribute;
124-
if (tableAttribute != null)
125-
{
126-
string targetTable = tableAttribute.Name;
127-
if (!string.IsNullOrEmpty(tableAttribute.Schema))
128-
targetTable = tableAttribute.Schema + "." + targetTable;
129-
130-
mergeDefinition.TargetTable = targetTable;
131-
}
116+
var typeAccessor = TypeAccessor.GetAccessor<TEntity>();
132117

133-
if (string.IsNullOrEmpty(mergeDefinition.TargetTable))
134-
mergeDefinition.TargetTable = entityType.Name;
118+
// don't overwrite existing
119+
if (mergeDefinition.TargetTable.IsNullOrEmpty())
120+
mergeDefinition.TargetTable = typeAccessor.TableSchema.HasValue() ? $"{typeAccessor.TableSchema}.{typeAccessor.TableName}" : typeAccessor.TableName;
135121

136-
foreach (PropertyDescriptor p in properties)
122+
foreach (var property in typeAccessor.GetProperties())
137123
{
138-
string sourceColumn = p.Name;
139-
string targetColumn = sourceColumn;
140-
string nativeType = SqlTypeMapping.NativeType(p.PropertyType);
141-
142-
var columnAttribute = p.Attributes
143-
.OfType<ColumnAttribute>()
144-
.FirstOrDefault();
145-
146-
if (columnAttribute != null)
147-
{
148-
if (columnAttribute.Name.HasValue())
149-
targetColumn = columnAttribute.Name;
150-
if (columnAttribute.TypeName.HasValue())
151-
nativeType = columnAttribute.TypeName;
152-
}
124+
string sourceColumn = property.Name;
125+
string targetColumn = property.Column;
126+
string nativeType = property.ColumnType ?? SqlTypeMapping.NativeType(property.MemberType);
153127

128+
// find existing map and update
154129
var mergeColumn = mergeDefinition.Columns.FirstOrAdd(
155130
m => m.SourceColumn == sourceColumn,
156131
() => new DataMergeColumn { SourceColumn = sourceColumn });
157132

158133
mergeColumn.TargetColumn = targetColumn;
159134
mergeColumn.NativeType = nativeType;
160-
161-
var keyAttribute = p.Attributes
162-
.OfType<KeyAttribute>()
163-
.FirstOrDefault();
164-
165-
if (keyAttribute != null)
166-
{
167-
mergeColumn.IsKey = true;
168-
mergeColumn.CanUpdate = false;
169-
}
170-
171-
var ignoreAttribute = p.Attributes
172-
.OfType<NotMappedAttribute>()
173-
.FirstOrDefault();
174-
175-
if (ignoreAttribute != null)
176-
mergeColumn.IsIgnored = true;
135+
mergeColumn.IsKey = property.IsKey;
136+
mergeColumn.CanUpdate = !property.IsKey && !property.IsDatabaseGenerated && !property.IsConcurrencyCheck;
137+
mergeColumn.CanInsert = !property.IsDatabaseGenerated && !property.IsConcurrencyCheck;
138+
mergeColumn.IsIgnored = property.IsNotMapped;
177139
}
178140
}
179141

src/FluentCommand.SqlServer/SqlTypeMapping.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,11 @@ public static class SqlTypeMapping
2020
{typeof(TimeSpan), "time"},
2121
{typeof(DateTime), "datetime2"},
2222
{typeof(DateTimeOffset), "datetimeoffset"},
23-
{typeof(Guid), "uniqueidentifier"}
23+
{typeof(Guid), "uniqueidentifier"},
24+
#if NET6_0_OR_GREATER
25+
{typeof(DateOnly), "date"},
26+
{typeof(TimeOnly), "time"},
27+
#endif
2428
};
2529

2630
/// <summary>

src/FluentCommand/Reflection/IMemberInformation.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ public interface IMemberInformation
3333
/// </value>
3434
string Column { get; }
3535

36+
/// <summary>
37+
/// Gets the database provider specific data type of the column the property is mapped to
38+
/// </summary>
39+
/// <value>
40+
/// The database provider specific data type of the column the property is mapped to
41+
/// </value>
42+
string ColumnType { get; }
43+
44+
/// <summary>
45+
/// Gets the zero-based order of the column the property is mapped to
46+
/// </summary>
47+
/// <value>
48+
/// The zero-based order of the column the property is mapped to
49+
/// </value>
50+
int? ColumnOrder { get; }
51+
3652
/// <summary>
3753
/// Gets a value indicating that this property is the unique identify for the entity
3854
/// </summary>

src/FluentCommand/Reflection/MemberAccessor.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,22 @@ protected MemberAccessor(MemberInfo memberInfo)
7272
/// </value>
7373
public string Column => _columnAttribute.Value?.Name ?? Name;
7474

75+
/// <summary>
76+
/// Gets the database provider specific data type of the column the property is mapped to
77+
/// </summary>
78+
/// <value>
79+
/// The database provider specific data type of the column the property is mapped to
80+
/// </value>
81+
public string ColumnType => _columnAttribute.Value?.TypeName;
82+
83+
/// <summary>
84+
/// Gets the zero-based order of the column the property is mapped to
85+
/// </summary>
86+
/// <value>
87+
/// The zero-based order of the column the property is mapped to
88+
/// </value>
89+
public int? ColumnOrder => _columnAttribute.Value?.Order;
90+
7591
/// <summary>
7692
/// Gets a value indicating that this property is the unique identify for the entity
7793
/// </summary>
@@ -170,7 +186,7 @@ public override bool Equals(object obj)
170186
/// Returns a hash code for this instance.
171187
/// </summary>
172188
/// <returns>
173-
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
189+
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
174190
/// </returns>
175191
public override int GetHashCode()
176192
{

test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,38 @@ public void BuildMergeDataTests()
126126
Output.WriteLine("MergeStatement:");
127127
Output.WriteLine(sql);
128128
}
129+
129130
[Fact]
130-
public void BuildMergeDataTypeTests()
131+
public async System.Threading.Tasks.Task BuildTableSqlTest()
131132
{
132133
var definition = new DataMergeDefinition();
133134

134135
DataMergeDefinition.AutoMap<DataType>(definition);
135136
definition.Columns.Should().NotBeNullOrEmpty();
137+
definition.TargetTable = "dbo.DataType";
138+
139+
var column = definition.Columns.Find(c => c.SourceColumn == "Id");
140+
column.Should().NotBeNull();
136141

142+
column.IsKey = true;
143+
column.CanUpdate = false;
144+
145+
var tableStatement = DataMergeGenerator.BuildTable(definition);
146+
tableStatement.Should().NotBeNull();
147+
await Verifier
148+
.Verify(tableStatement)
149+
.UseDirectory("Snapshots")
150+
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));
151+
152+
}
153+
154+
[Fact]
155+
public async System.Threading.Tasks.Task BuildMergeDataTypeTests()
156+
{
157+
var definition = new DataMergeDefinition();
158+
159+
DataMergeDefinition.AutoMap<DataType>(definition);
160+
definition.Columns.Should().NotBeNullOrEmpty();
137161
definition.TargetTable = "dbo.DataType";
138162

139163
var column = definition.Columns.Find(c => c.SourceColumn == "Id");
@@ -144,8 +168,7 @@ public void BuildMergeDataTypeTests()
144168

145169
var users = new List<DataType>
146170
{
147-
new DataType
148-
{
171+
new() {
149172
Id = 1,
150173
Name = "Test1",
151174
Boolean = false,
@@ -154,8 +177,8 @@ public void BuildMergeDataTypeTests()
154177
Float = 200.20F,
155178
Double = 300.35,
156179
Decimal = 456.12M,
157-
DateTime = DateTime.Now,
158-
DateTimeOffset = DateTimeOffset.Now,
180+
DateTime = new DateTime(2024, 5, 1, 8, 0, 0),
181+
DateTimeOffset = new DateTimeOffset(2024, 5, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
159182
Guid = Guid.Empty,
160183
TimeSpan = TimeSpan.FromHours(1),
161184
DateOnly = new DateOnly(2022, 12, 1),
@@ -166,15 +189,14 @@ public void BuildMergeDataTypeTests()
166189
FloatNull = 200.20F,
167190
DoubleNull = 300.35,
168191
DecimalNull = 456.12M,
169-
DateTimeNull = DateTime.Now,
170-
DateTimeOffsetNull = DateTimeOffset.Now,
192+
DateTimeNull = new DateTime(2024, 4, 1, 8, 0, 0),
193+
DateTimeOffsetNull = new DateTimeOffset(2024, 4, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
171194
GuidNull = Guid.Empty,
172195
TimeSpanNull = TimeSpan.FromHours(1),
173196
DateOnlyNull = new DateOnly(2022, 12, 1),
174197
TimeOnlyNull = new TimeOnly(1, 30, 0),
175198
},
176-
new DataType
177-
{
199+
new() {
178200
Id = 2,
179201
Name = "Test2",
180202
Boolean = true,
@@ -183,8 +205,8 @@ public void BuildMergeDataTypeTests()
183205
Float = 600.20F,
184206
Double = 700.35,
185207
Decimal = 856.12M,
186-
DateTime = DateTime.Now,
187-
DateTimeOffset = DateTimeOffset.Now,
208+
DateTime = new DateTime(2024, 5, 1, 8, 0, 0),
209+
DateTimeOffset = new DateTimeOffset(2024, 5, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
188210
Guid = Guid.Empty,
189211
TimeSpan = TimeSpan.FromHours(2),
190212
DateOnly = new DateOnly(2022, 12, 12),
@@ -194,11 +216,35 @@ public void BuildMergeDataTypeTests()
194216

195217
var listDataReader = new ListDataReader<DataType>(users);
196218

197-
var sql = DataMergeGenerator.BuildMerge(definition, listDataReader);
198-
sql.Should().NotBeNullOrEmpty();
219+
var mergeDataStatement = DataMergeGenerator.BuildMerge(definition, listDataReader);
220+
mergeDataStatement.Should().NotBeNullOrEmpty();
221+
await Verifier
222+
.Verify(mergeDataStatement)
223+
.UseDirectory("Snapshots")
224+
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));
225+
}
199226

200-
Output.WriteLine("MergeStatement:");
201-
Output.WriteLine(sql);
227+
[Fact]
228+
public async System.Threading.Tasks.Task BuildMergeDataTableTests()
229+
{
230+
var definition = new DataMergeDefinition();
231+
232+
DataMergeDefinition.AutoMap<DataType>(definition);
233+
definition.Columns.Should().NotBeNullOrEmpty();
234+
definition.TargetTable = "dbo.DataType";
235+
236+
var column = definition.Columns.Find(c => c.SourceColumn == "Id");
237+
column.Should().NotBeNull();
238+
239+
column.IsKey = true;
240+
column.CanUpdate = false;
241+
242+
var mergeStatement = DataMergeGenerator.BuildMerge(definition);
243+
mergeStatement.Should().NotBeNull();
244+
await Verifier
245+
.Verify(mergeStatement)
246+
.UseDirectory("Snapshots")
247+
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));
202248
}
203249

204250
[Fact]

test/FluentCommand.SqlServer.Tests/FluentCommand.SqlServer.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<PackageReference Include="Testcontainers.Azurite" Version="3.8.0" />
3939
<PackageReference Include="Testcontainers.MsSql" Version="3.8.0" />
4040
<PackageReference Include="Testcontainers.Redis" Version="3.8.0" />
41+
<PackageReference Include="Verify.Xunit" Version="24.1.0" />
4142
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
4243
<PrivateAssets>all</PrivateAssets>
4344
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>

0 commit comments

Comments
 (0)