You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
675 lines
26 KiB
C#
675 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading.Tasks;
|
|
using Haoliang.Models.Template;
|
|
using Haoliang.Models.Device;
|
|
using Haoliang.Models.DataCollection;
|
|
using Haoliang.Data.Repositories;
|
|
|
|
namespace Haoliang.Core.Services
|
|
{
|
|
public interface ITemplateValidationService
|
|
{
|
|
Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template);
|
|
Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template);
|
|
Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template);
|
|
Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId);
|
|
Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData);
|
|
Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template);
|
|
Task<ValidationReport> ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template);
|
|
Task<IEnumerable<TagValidationResult>> ValidateDeviceDataAsync(IEnumerable<TagData> deviceTags, int templateId);
|
|
Task<bool> ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2);
|
|
}
|
|
|
|
public class TemplateValidationService : ITemplateValidationService
|
|
{
|
|
private readonly ITemplateRepository _templateRepository;
|
|
private readonly ITagMappingRepository _tagMappingRepository;
|
|
private readonly ILoggingService _loggingService;
|
|
|
|
public TemplateValidationService(
|
|
ITemplateRepository templateRepository,
|
|
ITagMappingRepository tagMappingRepository,
|
|
ILoggingService loggingService)
|
|
{
|
|
_templateRepository = templateRepository;
|
|
_tagMappingRepository = tagMappingRepository;
|
|
_loggingService = loggingService;
|
|
}
|
|
|
|
public async Task<bool> ValidateTemplateStructureAsync(CNCBrandTemplate template)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
// Check basic structure
|
|
if (template == null)
|
|
{
|
|
errors.Add(new ValidationError { Field = "template", Message = "Template cannot be null" });
|
|
return false;
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(template.TemplateName))
|
|
{
|
|
errors.Add(new ValidationError { Field = "templateName", Message = "Template name is required" });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(template.BrandName))
|
|
{
|
|
errors.Add(new ValidationError { Field = "brandName", Message = "Brand name is required" });
|
|
}
|
|
|
|
if (template.Tags == null || !template.Tags.Any())
|
|
{
|
|
errors.Add(new ValidationError { Field = "tags", Message = "At least one tag must be defined" });
|
|
}
|
|
|
|
// Validate tag structure
|
|
if (template.Tags != null)
|
|
{
|
|
for (int i = 0; i < template.Tags.Count; i++)
|
|
{
|
|
var tag = template.Tags[i];
|
|
var tagErrors = ValidateTagStructure(tag, $"tags[{i}]");
|
|
errors.AddRange(tagErrors);
|
|
}
|
|
}
|
|
|
|
// Validate data processing rules
|
|
if (template.DataProcessingRules != null)
|
|
{
|
|
foreach (var rule in template.DataProcessingRules)
|
|
{
|
|
var ruleErrors = ValidateDataProcessingRule(rule);
|
|
errors.AddRange(ruleErrors);
|
|
}
|
|
}
|
|
|
|
// Log validation results
|
|
if (errors.Any())
|
|
{
|
|
await _loggingService.LogWarningAsync($"Template structure validation failed with {errors.Count} errors");
|
|
}
|
|
else
|
|
{
|
|
await _loggingService.LogInfoAsync("Template structure validation passed");
|
|
}
|
|
|
|
return !errors.Any();
|
|
}
|
|
|
|
public async Task<bool> ValidateTagMappingsAsync(CNCBrandTemplate template)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
if (template.Tags == null || !template.Tags.Any())
|
|
{
|
|
errors.Add(new ValidationError { Field = "tags", Message = "No tags defined to validate" });
|
|
return false;
|
|
}
|
|
|
|
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId);
|
|
var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);
|
|
|
|
foreach (var tag in template.Tags)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(tag.DeviceTagId))
|
|
{
|
|
// Check if mapping exists for device tag
|
|
if (!mappingDict.ContainsKey(tag.DeviceTagId))
|
|
{
|
|
errors.Add(new ValidationError
|
|
{
|
|
Field = $"tags[{tag.SystemTagId}].deviceTagId",
|
|
Message = $"No mapping found for device tag '{tag.DeviceTagId}'"
|
|
});
|
|
}
|
|
else
|
|
{
|
|
var mapping = mappingDict[tag.DeviceTagId];
|
|
if (!mapping.IsActive)
|
|
{
|
|
errors.Add(new ValidationError
|
|
{
|
|
Field = $"tags[{tag.SystemTagId}].deviceTagId",
|
|
Message = $"Mapping for device tag '{tag.DeviceTagId}' is inactive"
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return !errors.Any();
|
|
}
|
|
|
|
public async Task<bool> ValidateDataParsingRulesAsync(CNCBrandTemplate template)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
if (template.DataProcessingRules == null || !template.DataProcessingRules.Any())
|
|
{
|
|
return true; // No rules to validate
|
|
}
|
|
|
|
foreach (var rule in template.DataProcessingRules)
|
|
{
|
|
var ruleErrors = ValidateDataProcessingRule(rule);
|
|
errors.AddRange(ruleErrors);
|
|
}
|
|
|
|
return !errors.Any();
|
|
}
|
|
|
|
public async Task<IEnumerable<ValidationError>> ValidateTemplateForDeviceAsync(int templateId, int deviceId)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
var template = await _templateRepository.GetByIdAsync(templateId);
|
|
if (template == null)
|
|
{
|
|
errors.Add(new ValidationError { Field = "template", Message = "Template not found" });
|
|
return errors;
|
|
}
|
|
|
|
var device = await _templateRepository.GetDeviceByIdAsync(deviceId); // Assuming this method exists
|
|
if (device == null)
|
|
{
|
|
errors.Add(new ValidationError { Field = "device", Message = "Device not found" });
|
|
return errors;
|
|
}
|
|
|
|
// Check if template is compatible with device brand
|
|
if (template.BrandName != device.DeviceBrand)
|
|
{
|
|
errors.Add(new ValidationError
|
|
{
|
|
Field = "template.brand",
|
|
Message = $"Template brand '{template.BrandName}' does not match device brand '{device.DeviceBrand}'"
|
|
});
|
|
}
|
|
|
|
// Check for missing required tags
|
|
var missingTags = await GetMissingRequiredTagsAsync(template);
|
|
if (missingTags.Any())
|
|
{
|
|
errors.Add(new ValidationError
|
|
{
|
|
Field = "tags",
|
|
Message = $"Missing required tags: {string.Join(", ", missingTags)}"
|
|
});
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
public async Task<bool> TestTemplateDataParsingAsync(CNCBrandTemplate template, string sampleData)
|
|
{
|
|
try
|
|
{
|
|
// Parse sample data
|
|
var document = JsonDocument.Parse(sampleData);
|
|
var root = document.RootElement;
|
|
|
|
// Extract device data
|
|
var deviceTags = new List<TagData>();
|
|
|
|
if (root.TryGetProperty("tags", out var tagsElement))
|
|
{
|
|
foreach (var tagElement in tagsElement.EnumerateArray())
|
|
{
|
|
var tag = new TagData
|
|
{
|
|
Id = tagElement.GetProperty("id").GetString(),
|
|
Desc = tagElement.GetProperty("desc").GetString(),
|
|
Quality = tagElement.GetProperty("quality").GetString(),
|
|
Time = DateTime.Parse(tagElement.GetProperty("time").GetString())
|
|
};
|
|
|
|
if (tagElement.TryGetProperty("value", out var valueElement))
|
|
{
|
|
tag.Value = ParseTagValue(valueElement);
|
|
}
|
|
|
|
deviceTags.Add(tag);
|
|
}
|
|
}
|
|
|
|
// Test tag mapping
|
|
var mappingResult = await ValidateDeviceDataAsync(deviceTags, template.TemplateId);
|
|
if (mappingResult.Any(r => !r.IsValid))
|
|
{
|
|
await _loggingService.LogWarningAsync($"Template data parsing test failed with {mappingResult.Count(r => !r.IsValid)} validation errors");
|
|
return false;
|
|
}
|
|
|
|
// Test data processing rules
|
|
if (template.DataProcessingRules != null)
|
|
{
|
|
foreach (var rule in template.DataProcessingRules)
|
|
{
|
|
if (!await ExecuteDataProcessingRuleAsync(rule, deviceTags))
|
|
{
|
|
await _loggingService.LogWarningAsync($"Data processing rule '{rule.RuleName}' failed during test");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
await _loggingService.LogInfoAsync("Template data parsing test passed");
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _loggingService.LogErrorAsync($"Template data parsing test failed: {ex.Message}", ex);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public async Task<IEnumerable<string>> GetMissingRequiredTagsAsync(CNCBrandTemplate template)
|
|
{
|
|
var missingTags = new List<string>();
|
|
|
|
if (template == null || template.Tags == null)
|
|
return missingTags;
|
|
|
|
var requiredTags = template.Tags
|
|
.Where(t => t.IsRequired)
|
|
.Select(t => t.DeviceTagId)
|
|
.Where(id => !string.IsNullOrWhiteSpace(id))
|
|
.ToList();
|
|
|
|
if (!requiredTags.Any())
|
|
return missingTags;
|
|
|
|
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(template.TemplateId);
|
|
var mappedTags = mappings
|
|
.Where(m => m.IsActive)
|
|
.Select(m => m.DeviceTagId)
|
|
.ToHashSet();
|
|
|
|
foreach (var requiredTag in requiredTags)
|
|
{
|
|
if (!mappedTags.Contains(requiredTag))
|
|
{
|
|
missingTags.Add(requiredTag);
|
|
}
|
|
}
|
|
|
|
return missingTags;
|
|
}
|
|
|
|
public async Task<ValidationReport> ValidateTemplateComprehensivelyAsync(CNCBrandTemplate template)
|
|
{
|
|
var report = new ValidationReport
|
|
{
|
|
TemplateId = template.TemplateId,
|
|
TemplateName = template.TemplateName,
|
|
ValidationTime = DateTime.Now,
|
|
Checks = new List<ValidationCheck>
|
|
{
|
|
new ValidationCheck
|
|
{
|
|
Name = "Structure Validation",
|
|
Passed = await ValidateTemplateStructureAsync(template),
|
|
Errors = new List<ValidationError>()
|
|
},
|
|
new ValidationCheck
|
|
{
|
|
Name = "Tag Mapping Validation",
|
|
Passed = await ValidateTagMappingsAsync(template),
|
|
Errors = new List<ValidationError>()
|
|
},
|
|
new ValidationCheck
|
|
{
|
|
Name = "Data Parsing Rules Validation",
|
|
Passed = await ValidateDataProcessingRulesAsync(template),
|
|
Errors = new List<ValidationError>()
|
|
}
|
|
}
|
|
};
|
|
|
|
// Aggregate all errors
|
|
report.HasErrors = report.Checks.Any(c => !c.Passed);
|
|
report.TotalChecks = report.Checks.Count;
|
|
report.PassedChecks = report.Checks.Count(c => c.Passed);
|
|
report.FailedChecks = report.Checks.Count(c => !c.Passed);
|
|
|
|
await _loggingService.LogInformationAsync($"Comprehensive template validation: {report.PassedChecks}/{report.TotalChecks} checks passed");
|
|
return report;
|
|
}
|
|
|
|
public async Task<IEnumerable<TagValidationResult>> ValidateDeviceDataAsync(IEnumerable<TagData> deviceTags, int templateId)
|
|
{
|
|
var results = new List<TagValidationResult>();
|
|
var template = await _templateRepository.GetByIdAsync(templateId);
|
|
|
|
if (template == null)
|
|
{
|
|
results.Add(new TagValidationResult
|
|
{
|
|
SystemTagId = "template",
|
|
IsValid = false,
|
|
ErrorMessage = "Template not found"
|
|
});
|
|
return results;
|
|
}
|
|
|
|
var templateTags = template.Tags ?? new List<TagTemplate>();
|
|
var mappings = await _tagMappingRepository.GetMappingsByTemplateAsync(templateId);
|
|
var mappingDict = mappings.ToDictionary(m => m.DeviceTagId);
|
|
|
|
foreach (var templateTag in templateTags)
|
|
{
|
|
var result = new TagValidationResult
|
|
{
|
|
SystemTagId = templateTag.SystemTagId,
|
|
ExpectedDataType = templateTag.DataType,
|
|
IsRequired = templateTag.IsRequired
|
|
};
|
|
|
|
if (!string.IsNullOrWhiteSpace(templateTag.DeviceTagId))
|
|
{
|
|
// Find corresponding device tag
|
|
var deviceTag = deviceTags.FirstOrDefault(t => t.Id == templateTag.DeviceTagId);
|
|
|
|
if (deviceTag == null)
|
|
{
|
|
if (templateTag.IsRequired)
|
|
{
|
|
result.IsValid = false;
|
|
result.ErrorMessage = $"Required device tag '{templateTag.DeviceTagId}' not found";
|
|
}
|
|
else
|
|
{
|
|
result.IsValid = true;
|
|
result.Message = "Optional tag not present";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Validate device tag
|
|
result.DeviceTagId = templateTag.DeviceTagId;
|
|
result.DeviceValue = deviceTag.Value;
|
|
result.Quality = deviceTag.Quality;
|
|
|
|
if (!ValidateTagValue(deviceTag, templateTag))
|
|
{
|
|
result.IsValid = false;
|
|
result.ErrorMessage = $"Tag value validation failed for '{templateTag.DeviceTagId}'";
|
|
}
|
|
else
|
|
{
|
|
result.IsValid = true;
|
|
result.MappedValue = ApplyTagMapping(deviceTag, mappingDict[templateTag.DeviceTagId]);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result.IsValid = true;
|
|
result.Message = "No device tag mapping defined";
|
|
}
|
|
|
|
results.Add(result);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
public async Task<bool> ValidateTemplateCompatibilityAsync(CNCBrandTemplate template1, CNCBrandTemplate template2)
|
|
{
|
|
if (template1 == null || template2 == null)
|
|
return false;
|
|
|
|
if (template1.TemplateId == template2.TemplateId)
|
|
return true; // Same template is always compatible
|
|
|
|
// Check if same brand
|
|
if (template1.BrandName != template2.BrandName)
|
|
{
|
|
await _loggingService.LogInfoAsync($"Templates have different brands: {template1.BrandName} vs {template2.BrandName}");
|
|
return false;
|
|
}
|
|
|
|
// Check tag compatibility
|
|
var tags1 = template1.Tags ?? new List<TagTemplate>();
|
|
var tags2 = template2.Tags ?? new List<TagTemplate>();
|
|
|
|
// Count common tags
|
|
var commonTags = tags1.Intersect(tags2, new TagTemplateComparer());
|
|
var compatibilityScore = (double)commonTags.Count() / Math.Max(tags1.Count, tags2.Count);
|
|
|
|
await _loggingService.LogInfoAsync($"Template compatibility score: {compatibilityScore:P1}");
|
|
return compatibilityScore >= 0.8; // 80% compatibility threshold
|
|
}
|
|
|
|
private List<ValidationError> ValidateTagStructure(TagTemplate tag, string fieldPath)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
if (string.IsNullOrWhiteSpace(tag.SystemTagId))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"{fieldPath}.systemTagId", Message = "System tag ID is required" });
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(tag.DeviceTagId))
|
|
{
|
|
if (string.IsNullOrWhiteSpace(tag.DataType))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = "Data type is required when device tag ID is specified" });
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(tag.DataType))
|
|
{
|
|
var validTypes = new[] { "int", "decimal", "bool", "string", "datetime", "double" };
|
|
if (!validTypes.Contains(tag.DataType.ToLower()))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"{fieldPath}.dataType", Message = $"Invalid data type: {tag.DataType}" });
|
|
}
|
|
}
|
|
|
|
// Validate regex pattern if provided
|
|
if (!string.IsNullOrWhiteSpace(tag.ValidationRegex))
|
|
{
|
|
try
|
|
{
|
|
Regex.IsMatch("test", tag.ValidationRegex); // Test if regex is valid
|
|
}
|
|
catch
|
|
{
|
|
errors.Add(new ValidationError { Field = $"{fieldPath}.validationRegex", Message = "Invalid regular expression pattern" });
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
private List<ValidationError> ValidateDataProcessingRule(DataProcessingRule rule)
|
|
{
|
|
var errors = new List<ValidationError>();
|
|
|
|
if (string.IsNullOrWhiteSpace(rule.RuleName))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].ruleName", Message = "Rule name is required" });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(rule.Condition))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Condition is required" });
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(rule.Action))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].action", Message = "Action is required" });
|
|
}
|
|
|
|
// Validate condition syntax (simple check)
|
|
if (!string.IsNullOrWhiteSpace(rule.Condition) && !IsValidCondition(rule.Condition))
|
|
{
|
|
errors.Add(new ValidationError { Field = $"dataProcessingRules[{rule.RuleName}].condition", Message = "Invalid condition syntax" });
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
private bool IsValidCondition(string condition)
|
|
{
|
|
// Simple condition validation (in real implementation, use expression parser)
|
|
var validOperators = new[] { ">", "<", ">=", "<=", "==", "!=", "&&", "||", "(", ")", "true", "false" };
|
|
var parts = condition.Split(' ');
|
|
|
|
foreach (var part in parts)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(part) && !validOperators.Contains(part) && !double.TryParse(part, out _))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private object ParseTagValue(JsonElement valueElement)
|
|
{
|
|
if (valueElement.ValueKind == JsonValueKind.String)
|
|
{
|
|
return valueElement.GetString();
|
|
}
|
|
else if (valueElement.ValueKind == JsonValueKind.Number)
|
|
{
|
|
if (valueElement.TryGetInt32(out int intValue))
|
|
return intValue;
|
|
else if (valueElement.TryGetDecimal(out decimal decimalValue))
|
|
return decimalValue;
|
|
else
|
|
return valueElement.GetDouble();
|
|
}
|
|
else if (valueElement.ValueKind == JsonValueKind.True || valueElement.ValueKind == JsonValueKind.False)
|
|
{
|
|
return valueElement.GetBoolean();
|
|
}
|
|
else
|
|
{
|
|
return valueElement.ToString();
|
|
}
|
|
}
|
|
|
|
private bool ValidateTagValue(TagData deviceTag, TagTemplate templateTag)
|
|
{
|
|
if (deviceTag.Value == null)
|
|
return !templateTag.IsRequired;
|
|
|
|
try
|
|
{
|
|
// Validate data type
|
|
switch (templateTag.DataType.ToLower())
|
|
{
|
|
case "int":
|
|
return int.TryParse(deviceTag.Value.ToString(), out _);
|
|
case "decimal":
|
|
return decimal.TryParse(deviceTag.Value.ToString(), out _);
|
|
case "bool":
|
|
return bool.TryParse(deviceTag.Value.ToString(), out _);
|
|
case "datetime":
|
|
return DateTime.TryParse(deviceTag.Value.ToString(), out _);
|
|
default:
|
|
return true; // String type accepts any value
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private TagData ApplyTagMapping(TagData deviceTag, TagMapping mapping)
|
|
{
|
|
return new TagData
|
|
{
|
|
Id = mapping.SystemTagId,
|
|
Desc = mapping.Description ?? deviceTag.Desc,
|
|
Quality = deviceTag.Quality,
|
|
Time = deviceTag.Time,
|
|
Value = ConvertTagValue(deviceTag.Value, mapping.DataType, mapping.ConversionFormula)
|
|
};
|
|
}
|
|
|
|
private async Task<bool> ExecuteDataProcessingRuleAsync(DataProcessingRule rule, IEnumerable<TagData> deviceTags)
|
|
{
|
|
// Simple rule execution (in real implementation, use expression parser)
|
|
try
|
|
{
|
|
// This is a simplified implementation
|
|
// In a real system, you would parse and execute the condition and action
|
|
|
|
if (rule.Condition.Contains("temperature") && deviceTags.Any(t => t.Id == "temperature"))
|
|
{
|
|
var tempTag = deviceTags.First(t => t.Id == "temperature");
|
|
var tempValue = Convert.ToDouble(tempTag.Value);
|
|
|
|
if (tempValue > 100 && rule.Action.Contains("alert"))
|
|
{
|
|
return true; // Rule executed successfully
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _loggingService.LogErrorAsync($"Failed to execute data processing rule '{rule.RuleName}': {ex.Message}");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Supporting classes and interfaces
|
|
public class ValidationReport
|
|
{
|
|
public int TemplateId { get; set; }
|
|
public string TemplateName { get; set; }
|
|
public DateTime ValidationTime { get; set; }
|
|
public List<ValidationCheck> Checks { get; set; }
|
|
public bool HasErrors { get; set; }
|
|
public int TotalChecks { get; set; }
|
|
public int PassedChecks { get; set; }
|
|
public int FailedChecks { get; set; }
|
|
}
|
|
|
|
public class ValidationCheck
|
|
{
|
|
public string Name { get; set; }
|
|
public bool Passed { get; set; }
|
|
public List<ValidationError> Errors { get; set; }
|
|
}
|
|
|
|
public class TagValidationResult
|
|
{
|
|
public string SystemTagId { get; set; }
|
|
public string DeviceTagId { get; set; }
|
|
public object DeviceValue { get; set; }
|
|
public object MappedValue { get; set; }
|
|
public string ExpectedDataType { get; set; }
|
|
public string Quality { get; set; }
|
|
public bool IsRequired { get; set; }
|
|
public bool IsValid { get; set; }
|
|
public string ErrorMessage { get; set; }
|
|
public string Message { get; set; }
|
|
}
|
|
|
|
public class TagTemplateComparer : IEqualityComparer<TagTemplate>
|
|
{
|
|
public bool Equals(TagTemplate x, TagTemplate y)
|
|
{
|
|
return x?.SystemTagId == y?.SystemTagId;
|
|
}
|
|
|
|
public int GetHashCode(TagTemplate obj)
|
|
{
|
|
return obj?.SystemTagId?.GetHashCode() ?? 0;
|
|
}
|
|
}
|
|
} |