feat(transport): round-trip ElementDataType for List attributes
Add DataType? ElementDataType to TemplateAttributeDto (optional, default null for backward-compat with old bundles). Map it in both directions in EntitySerializer (export + FromBundleContent) and in all three TemplateAttribute construction sites in BundleImporter (BuildTemplate, SyncTemplateAttributesAsync add-path, and SyncTemplateAttributesAsync update-path including change-detection). Two new round-trip tests in EntitySerializerTests confirm List attributes survive export→import and that old DTOs with null ElementDataType import cleanly.
This commit is contained in:
@@ -1045,6 +1045,7 @@ public sealed class BundleImporter : IBundleImporter
|
|||||||
IsLocked = a.IsLocked,
|
IsLocked = a.IsLocked,
|
||||||
Description = a.Description,
|
Description = a.Description,
|
||||||
DataSourceReference = a.DataSourceReference,
|
DataSourceReference = a.DataSourceReference,
|
||||||
|
ElementDataType = a.ElementDataType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
foreach (var al in dto.Alarms)
|
foreach (var al in dto.Alarms)
|
||||||
@@ -1122,7 +1123,8 @@ public sealed class BundleImporter : IBundleImporter
|
|||||||
current.DataType != attrDto.DataType ||
|
current.DataType != attrDto.DataType ||
|
||||||
current.IsLocked != attrDto.IsLocked ||
|
current.IsLocked != attrDto.IsLocked ||
|
||||||
!string.Equals(current.Description, attrDto.Description, StringComparison.Ordinal) ||
|
!string.Equals(current.Description, attrDto.Description, StringComparison.Ordinal) ||
|
||||||
!string.Equals(current.DataSourceReference, attrDto.DataSourceReference, StringComparison.Ordinal);
|
!string.Equals(current.DataSourceReference, attrDto.DataSourceReference, StringComparison.Ordinal) ||
|
||||||
|
current.ElementDataType != attrDto.ElementDataType;
|
||||||
if (!changed) continue;
|
if (!changed) continue;
|
||||||
|
|
||||||
current.Value = attrDto.Value;
|
current.Value = attrDto.Value;
|
||||||
@@ -1130,6 +1132,7 @@ public sealed class BundleImporter : IBundleImporter
|
|||||||
current.IsLocked = attrDto.IsLocked;
|
current.IsLocked = attrDto.IsLocked;
|
||||||
current.Description = attrDto.Description;
|
current.Description = attrDto.Description;
|
||||||
current.DataSourceReference = attrDto.DataSourceReference;
|
current.DataSourceReference = attrDto.DataSourceReference;
|
||||||
|
current.ElementDataType = attrDto.ElementDataType;
|
||||||
await _templateRepo.UpdateTemplateAttributeAsync(current, ct).ConfigureAwait(false);
|
await _templateRepo.UpdateTemplateAttributeAsync(current, ct).ConfigureAwait(false);
|
||||||
await _auditService.LogAsync(
|
await _auditService.LogAsync(
|
||||||
user,
|
user,
|
||||||
@@ -1158,6 +1161,7 @@ public sealed class BundleImporter : IBundleImporter
|
|||||||
IsLocked = attrDto.IsLocked,
|
IsLocked = attrDto.IsLocked,
|
||||||
Description = attrDto.Description,
|
Description = attrDto.Description,
|
||||||
DataSourceReference = attrDto.DataSourceReference,
|
DataSourceReference = attrDto.DataSourceReference,
|
||||||
|
ElementDataType = attrDto.ElementDataType,
|
||||||
};
|
};
|
||||||
ex.Attributes.Add(newAttr);
|
ex.Attributes.Add(newAttr);
|
||||||
await _auditService.LogAsync(
|
await _auditService.LogAsync(
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ public sealed record TemplateAttributeDto(
|
|||||||
DataType DataType,
|
DataType DataType,
|
||||||
bool IsLocked,
|
bool IsLocked,
|
||||||
string? Description,
|
string? Description,
|
||||||
string? DataSourceReference);
|
string? DataSourceReference,
|
||||||
|
DataType? ElementDataType = null);
|
||||||
|
|
||||||
public sealed record TemplateAlarmDto(
|
public sealed record TemplateAlarmDto(
|
||||||
string Name,
|
string Name,
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public sealed class EntitySerializer
|
|||||||
DataType: a.DataType,
|
DataType: a.DataType,
|
||||||
IsLocked: a.IsLocked,
|
IsLocked: a.IsLocked,
|
||||||
Description: a.Description,
|
Description: a.Description,
|
||||||
DataSourceReference: a.DataSourceReference)).ToList(),
|
DataSourceReference: a.DataSourceReference,
|
||||||
|
ElementDataType: a.ElementDataType)).ToList(),
|
||||||
Alarms: t.Alarms.Select(a => new TemplateAlarmDto(
|
Alarms: t.Alarms.Select(a => new TemplateAlarmDto(
|
||||||
Name: a.Name,
|
Name: a.Name,
|
||||||
Description: a.Description,
|
Description: a.Description,
|
||||||
@@ -203,6 +204,7 @@ public sealed class EntitySerializer
|
|||||||
IsLocked = a.IsLocked,
|
IsLocked = a.IsLocked,
|
||||||
Description = a.Description,
|
Description = a.Description,
|
||||||
DataSourceReference = a.DataSourceReference,
|
DataSourceReference = a.DataSourceReference,
|
||||||
|
ElementDataType = a.ElementDataType,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
foreach (var al in dto.Alarms)
|
foreach (var al in dto.Alarms)
|
||||||
|
|||||||
@@ -223,4 +223,71 @@ public sealed class EntitySerializerTests
|
|||||||
var sys = Assert.Single(aggregate.ExternalSystems);
|
var sys = Assert.Single(aggregate.ExternalSystems);
|
||||||
Assert.Null(sys.AuthConfiguration);
|
Assert.Null(sys.AuthConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Roundtrip_List_attribute_preserves_ElementDataType()
|
||||||
|
{
|
||||||
|
// A template with a DataType.List attribute whose element type is String.
|
||||||
|
var template = new Template("Pump") { Id = 1 };
|
||||||
|
template.Attributes.Add(new TemplateAttribute("Tags")
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
TemplateId = 1,
|
||||||
|
DataType = DataType.List,
|
||||||
|
ElementDataType = DataType.String,
|
||||||
|
Value = "[\"a\",\"b\"]",
|
||||||
|
IsLocked = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
var aggregate = MakeEmptyAggregate() with { Templates = new[] { template } };
|
||||||
|
|
||||||
|
var sut = new EntitySerializer();
|
||||||
|
var dto = sut.ToBundleContent(aggregate);
|
||||||
|
|
||||||
|
// Export side: DTO must carry ElementDataType.
|
||||||
|
var dtoTemplate = Assert.Single(dto.Templates);
|
||||||
|
var dtoAttr = Assert.Single(dtoTemplate.Attributes);
|
||||||
|
Assert.Equal(DataType.List, dtoAttr.DataType);
|
||||||
|
Assert.Equal(DataType.String, dtoAttr.ElementDataType);
|
||||||
|
|
||||||
|
// Import side: entity reconstructed from DTO preserves the value.
|
||||||
|
var roundTripped = sut.FromBundleContent(dto);
|
||||||
|
var rtTemplate = Assert.Single(roundTripped.Templates);
|
||||||
|
var rtAttr = Assert.Single(rtTemplate.Attributes);
|
||||||
|
Assert.Equal(DataType.List, rtAttr.DataType);
|
||||||
|
Assert.Equal(DataType.String, rtAttr.ElementDataType);
|
||||||
|
Assert.Equal("[\"a\",\"b\"]", rtAttr.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Roundtrip_scalar_attribute_with_null_ElementDataType_remains_null()
|
||||||
|
{
|
||||||
|
// Backward-compat: an old bundle DTO with null ElementDataType must not throw
|
||||||
|
// and must produce a scalar attribute with null ElementDataType.
|
||||||
|
var template = new Template("Sensor") { Id = 1 };
|
||||||
|
template.Attributes.Add(new TemplateAttribute("Pressure")
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
TemplateId = 1,
|
||||||
|
DataType = DataType.Double,
|
||||||
|
ElementDataType = null,
|
||||||
|
Value = "42.0",
|
||||||
|
IsLocked = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
var aggregate = MakeEmptyAggregate() with { Templates = new[] { template } };
|
||||||
|
|
||||||
|
var sut = new EntitySerializer();
|
||||||
|
var dto = sut.ToBundleContent(aggregate);
|
||||||
|
|
||||||
|
var dtoTemplate = Assert.Single(dto.Templates);
|
||||||
|
var dtoAttr = Assert.Single(dtoTemplate.Attributes);
|
||||||
|
Assert.Equal(DataType.Double, dtoAttr.DataType);
|
||||||
|
Assert.Null(dtoAttr.ElementDataType);
|
||||||
|
|
||||||
|
var roundTripped = sut.FromBundleContent(dto);
|
||||||
|
var rtAttr = Assert.Single(Assert.Single(roundTripped.Templates).Attributes);
|
||||||
|
Assert.Equal(DataType.Double, rtAttr.DataType);
|
||||||
|
Assert.Null(rtAttr.ElementDataType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user