Skip to content

Commit

Permalink
more data merge fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed May 6, 2024
1 parent 7686bcf commit 5fc18df
Show file tree
Hide file tree
Showing 10 changed files with 381 additions and 70 deletions.
13 changes: 12 additions & 1 deletion src/FluentCommand.SqlServer/Merge/DataMergeColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,21 @@ public DataMergeColumn()
public bool IsKey { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the column is ignored, not used by merge.
/// Gets or sets a value indicating whether the column is ignored, not used by merge.
/// </summary>
/// <value>
/// <c>true</c> if the column is ignored; otherwise, <c>false</c>.
/// </value>
public bool IsIgnored { get; set; }

/// <summary>
/// Converts to string.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
return $"Source: {SourceColumn}, Target: {TargetColumn}, NativeType: {NativeType}, Key: {IsKey}, Ignored: {IsIgnored}";
}
}
66 changes: 14 additions & 52 deletions src/FluentCommand.SqlServer/Merge/DataMergeDefinition.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

using FluentCommand.Extensions;
using FluentCommand.Reflection;

namespace FluentCommand.Merge;

Expand Down Expand Up @@ -116,64 +113,29 @@ public static DataMergeDefinition Create<TEntity>()
/// <param name="mergeDefinition">The merge definition up auto map to.</param>
public static void AutoMap<TEntity>(DataMergeDefinition mergeDefinition)
{
var entityType = typeof(TEntity);
var properties = TypeDescriptor.GetProperties(entityType);


var tableAttribute = Attribute.GetCustomAttribute(entityType, typeof(TableAttribute)) as TableAttribute;
if (tableAttribute != null)
{
string targetTable = tableAttribute.Name;
if (!string.IsNullOrEmpty(tableAttribute.Schema))
targetTable = tableAttribute.Schema + "." + targetTable;

mergeDefinition.TargetTable = targetTable;
}
var typeAccessor = TypeAccessor.GetAccessor<TEntity>();

if (string.IsNullOrEmpty(mergeDefinition.TargetTable))
mergeDefinition.TargetTable = entityType.Name;
// don't overwrite existing
if (mergeDefinition.TargetTable.IsNullOrEmpty())
mergeDefinition.TargetTable = typeAccessor.TableSchema.HasValue() ? $"{typeAccessor.TableSchema}.{typeAccessor.TableName}" : typeAccessor.TableName;

foreach (PropertyDescriptor p in properties)
foreach (var property in typeAccessor.GetProperties())
{
string sourceColumn = p.Name;
string targetColumn = sourceColumn;
string nativeType = SqlTypeMapping.NativeType(p.PropertyType);

var columnAttribute = p.Attributes
.OfType<ColumnAttribute>()
.FirstOrDefault();

if (columnAttribute != null)
{
if (columnAttribute.Name.HasValue())
targetColumn = columnAttribute.Name;
if (columnAttribute.TypeName.HasValue())
nativeType = columnAttribute.TypeName;
}
string sourceColumn = property.Name;
string targetColumn = property.Column;
string nativeType = property.ColumnType ?? SqlTypeMapping.NativeType(property.MemberType);

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

mergeColumn.TargetColumn = targetColumn;
mergeColumn.NativeType = nativeType;

var keyAttribute = p.Attributes
.OfType<KeyAttribute>()
.FirstOrDefault();

if (keyAttribute != null)
{
mergeColumn.IsKey = true;
mergeColumn.CanUpdate = false;
}

var ignoreAttribute = p.Attributes
.OfType<NotMappedAttribute>()
.FirstOrDefault();

if (ignoreAttribute != null)
mergeColumn.IsIgnored = true;
mergeColumn.IsKey = property.IsKey;
mergeColumn.CanUpdate = !property.IsKey && !property.IsDatabaseGenerated && !property.IsConcurrencyCheck;
mergeColumn.CanInsert = !property.IsDatabaseGenerated && !property.IsConcurrencyCheck;
mergeColumn.IsIgnored = property.IsNotMapped;
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/FluentCommand.SqlServer/SqlTypeMapping.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ public static class SqlTypeMapping
{typeof(TimeSpan), "time"},
{typeof(DateTime), "datetime2"},
{typeof(DateTimeOffset), "datetimeoffset"},
{typeof(Guid), "uniqueidentifier"}
{typeof(Guid), "uniqueidentifier"},
#if NET6_0_OR_GREATER
{typeof(DateOnly), "date"},
{typeof(TimeOnly), "time"},
#endif
};

/// <summary>
Expand Down
16 changes: 16 additions & 0 deletions src/FluentCommand/Reflection/IMemberInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,22 @@ public interface IMemberInformation
/// </value>
string Column { get; }

/// <summary>
/// Gets the database provider specific data type of the column the property is mapped to
/// </summary>
/// <value>
/// The database provider specific data type of the column the property is mapped to
/// </value>
string ColumnType { get; }

/// <summary>
/// Gets the zero-based order of the column the property is mapped to
/// </summary>
/// <value>
/// The zero-based order of the column the property is mapped to
/// </value>
int? ColumnOrder { get; }

/// <summary>
/// Gets a value indicating that this property is the unique identify for the entity
/// </summary>
Expand Down
18 changes: 17 additions & 1 deletion src/FluentCommand/Reflection/MemberAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ protected MemberAccessor(MemberInfo memberInfo)
/// </value>
public string Column => _columnAttribute.Value?.Name ?? Name;

/// <summary>
/// Gets the database provider specific data type of the column the property is mapped to
/// </summary>
/// <value>
/// The database provider specific data type of the column the property is mapped to
/// </value>
public string ColumnType => _columnAttribute.Value?.TypeName;

/// <summary>
/// Gets the zero-based order of the column the property is mapped to
/// </summary>
/// <value>
/// The zero-based order of the column the property is mapped to
/// </value>
public int? ColumnOrder => _columnAttribute.Value?.Order;

/// <summary>
/// Gets a value indicating that this property is the unique identify for the entity
/// </summary>
Expand Down Expand Up @@ -170,7 +186,7 @@ public override bool Equals(object obj)
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode()
{
Expand Down
76 changes: 61 additions & 15 deletions test/FluentCommand.SqlServer.Tests/DataMergeGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,38 @@ public void BuildMergeDataTests()
Output.WriteLine("MergeStatement:");
Output.WriteLine(sql);
}

[Fact]
public void BuildMergeDataTypeTests()
public async System.Threading.Tasks.Task BuildTableSqlTest()
{
var definition = new DataMergeDefinition();

DataMergeDefinition.AutoMap<DataType>(definition);
definition.Columns.Should().NotBeNullOrEmpty();
definition.TargetTable = "dbo.DataType";

var column = definition.Columns.Find(c => c.SourceColumn == "Id");
column.Should().NotBeNull();

column.IsKey = true;
column.CanUpdate = false;

var tableStatement = DataMergeGenerator.BuildTable(definition);
tableStatement.Should().NotBeNull();
await Verifier
.Verify(tableStatement)
.UseDirectory("Snapshots")
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));

}

[Fact]
public async System.Threading.Tasks.Task BuildMergeDataTypeTests()
{
var definition = new DataMergeDefinition();

DataMergeDefinition.AutoMap<DataType>(definition);
definition.Columns.Should().NotBeNullOrEmpty();
definition.TargetTable = "dbo.DataType";

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

var users = new List<DataType>
{
new DataType
{
new() {
Id = 1,
Name = "Test1",
Boolean = false,
Expand All @@ -154,8 +177,8 @@ public void BuildMergeDataTypeTests()
Float = 200.20F,
Double = 300.35,
Decimal = 456.12M,
DateTime = DateTime.Now,
DateTimeOffset = DateTimeOffset.Now,
DateTime = new DateTime(2024, 5, 1, 8, 0, 0),
DateTimeOffset = new DateTimeOffset(2024, 5, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
Guid = Guid.Empty,
TimeSpan = TimeSpan.FromHours(1),
DateOnly = new DateOnly(2022, 12, 1),
Expand All @@ -166,15 +189,14 @@ public void BuildMergeDataTypeTests()
FloatNull = 200.20F,
DoubleNull = 300.35,
DecimalNull = 456.12M,
DateTimeNull = DateTime.Now,
DateTimeOffsetNull = DateTimeOffset.Now,
DateTimeNull = new DateTime(2024, 4, 1, 8, 0, 0),
DateTimeOffsetNull = new DateTimeOffset(2024, 4, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
GuidNull = Guid.Empty,
TimeSpanNull = TimeSpan.FromHours(1),
DateOnlyNull = new DateOnly(2022, 12, 1),
TimeOnlyNull = new TimeOnly(1, 30, 0),
},
new DataType
{
new() {
Id = 2,
Name = "Test2",
Boolean = true,
Expand All @@ -183,8 +205,8 @@ public void BuildMergeDataTypeTests()
Float = 600.20F,
Double = 700.35,
Decimal = 856.12M,
DateTime = DateTime.Now,
DateTimeOffset = DateTimeOffset.Now,
DateTime = new DateTime(2024, 5, 1, 8, 0, 0),
DateTimeOffset = new DateTimeOffset(2024, 5, 1, 8, 0, 0, TimeSpan.FromHours(-6)),
Guid = Guid.Empty,
TimeSpan = TimeSpan.FromHours(2),
DateOnly = new DateOnly(2022, 12, 12),
Expand All @@ -194,11 +216,35 @@ public void BuildMergeDataTypeTests()

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

var sql = DataMergeGenerator.BuildMerge(definition, listDataReader);
sql.Should().NotBeNullOrEmpty();
var mergeDataStatement = DataMergeGenerator.BuildMerge(definition, listDataReader);
mergeDataStatement.Should().NotBeNullOrEmpty();
await Verifier
.Verify(mergeDataStatement)
.UseDirectory("Snapshots")
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));
}

Output.WriteLine("MergeStatement:");
Output.WriteLine(sql);
[Fact]
public async System.Threading.Tasks.Task BuildMergeDataTableTests()
{
var definition = new DataMergeDefinition();

DataMergeDefinition.AutoMap<DataType>(definition);
definition.Columns.Should().NotBeNullOrEmpty();
definition.TargetTable = "dbo.DataType";

var column = definition.Columns.Find(c => c.SourceColumn == "Id");
column.Should().NotBeNull();

column.IsKey = true;
column.CanUpdate = false;

var mergeStatement = DataMergeGenerator.BuildMerge(definition);
mergeStatement.Should().NotBeNull();
await Verifier
.Verify(mergeStatement)
.UseDirectory("Snapshots")
.AddScrubber(scrubber => scrubber.Replace(definition.TemporaryTable, "#MergeTable"));
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<PackageReference Include="Testcontainers.Azurite" Version="3.8.0" />
<PackageReference Include="Testcontainers.MsSql" Version="3.8.0" />
<PackageReference Include="Testcontainers.Redis" Version="3.8.0" />
<PackageReference Include="Verify.Xunit" Version="24.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
Expand Down
Loading

0 comments on commit 5fc18df

Please sign in to comment.