API Property Embedding Strategy
✅ Approved Approach
Frontend will NOT know about LinkProperty/NodeProperty tables.
Properties are embedded in asset DTOs (Pump, Valve, Tank) via JOIN operations in the controller layer.
🎯 The Pattern
What Frontend Sees
// Single API call
const pump = await fetch('/api/pumps/Pump-1');
// Complete response with all properties
{
"id": "Pump-1",
"fromNode": "J-100",
"toNode": "J-200",
"initStatus": 1, // From LinkProperty table
"initSetting": 1.0, // From LinkProperty table
"pattern": 5, // From LinkProperty table
"hCurve": 10, // From LinkProperty table
"eCost": 0.05, // From LinkProperty table
"eCurve": 3, // From LinkProperty table
"ePattern": 2 // From LinkProperty table
}
What Happens in the Backend
[HttpGet("{id}")]
public async Task<ActionResult<PumpDto>> GetPump(string id)
{
// Step 1: Get core entity
var pump = await _context.Pumps
.FirstOrDefaultAsync(p => p.Id == id);
if (pump == null)
return NotFound();
// Step 2: Get all LinkProperties for this pump
var properties = await _context.LinkProperties
.Where(lp => lp.LinkId == id)
.ToDictionaryAsync(lp => lp.PropertyId, lp => lp.PropertyValue);
// Step 3: Map both to DTO (embedding properties)
return Ok(MapToDto(pump, properties));
}
private PumpDto MapToDto(Pump pump, Dictionary<int, double> properties)
{
return new PumpDto
{
Id = pump.Id,
FromNode = pump.FromNode,
ToNode = pump.ToNode,
// Embed properties from LinkProperty table
InitStatus = GetIntProperty(properties, EN_LinkProperty.EN_INITSTATUS),
InitSetting = GetProperty(properties, EN_LinkProperty.EN_INITSETTING),
Pattern = GetIntProperty(properties, EN_LinkProperty.EN_LINKPATTERN),
HCurve = GetIntProperty(properties, EN_LinkProperty.EN_PUMP_HCURVE),
ECost = GetProperty(properties, EN_LinkProperty.EN_PUMP_ECOST),
ECurve = GetIntProperty(properties, EN_LinkProperty.EN_PUMP_ECURVE),
EPattern = GetIntProperty(properties, EN_LinkProperty.EN_PUMP_EPAT),
CreatedAt = pump.CreatedAt,
ModifiedAt = pump.ModifiedAt
};
}
// Helper methods for property extraction
private double? GetProperty(Dictionary<int, double> props, EN_LinkProperty propId)
{
return props.TryGetValue((int)propId, out var value) ? value : null;
}
private int? GetIntProperty(Dictionary<int, double> props, EN_LinkProperty propId)
{
return props.TryGetValue((int)propId, out var value) ? (int)value : null;
}
Benefits of This Approach
✅ Frontend Simplicity
- Single API call gets everything
- No knowledge of property tables required
- Consistent DTO structure
- Easy to work with
✅ Backend Flexibility
- Can add new properties without breaking API
- Properties stored normalized in database
- JOIN performance acceptable
- Clear separation of concerns
✅ Maintainability
- Backend owns the complexity
- Frontend stays simple
- Easy to understand and debug
- Standard REST patterns
Alternative Approaches (Rejected)
❌ Option: Expose LinkProperty/NodeProperty Tables
// Frontend would need TWO calls
const pump = await fetch('/api/pumps/Pump-1');
const properties = await fetch('/api/linkproperties?linkId=Pump-1');
// Then manually merge
const completePump = { ...pump, ...parseProperties(properties) };
Why Rejected:
- Exposes internal database structure
- Requires frontend to understand property IDs
- More API calls = slower
- Complex frontend logic
- Poor developer experience
Implementation Notes
Property ID Mapping
Link Property IDs (EN_LinkProperty enum):
- 4: EN_INITSTATUS
- 5: EN_INITSETTING
- 14: EN_PUMP_HCURVE
- 15: EN_LINKPATTERN
- 20: EN_PUMP_ECURVE
- 21: EN_PUMP_ECOST
- 22: EN_PUMP_EPAT
Node Property IDs (EN_NodeProperty enum):
- 3: EN_EMITTER
- 4: EN_INITQUAL
- 23: EN_TANK_KBULK
Default Value Handling
If LinkProperty/NodeProperty record doesn't exist:
- Return null (frontend interprets as default)
- Or return known default value
- Document defaults in API response
Summary
✅ Chosen Strategy:
- Properties embedded in DTOs
- Frontend stays simple
- Backend handles complexity
- Single API calls
- Clean abstraction
Result:
- Better developer experience
- Consistent API design
- Maintainable codebase
- Flexible for future changes
Related Documentation:
- DTO Inheritance Analysis:
DTO_Inheritance_Analysis.md - API Endpoints Reference:
APIEndpoints.md - Configuration API:
ConfigurationAPI.md
Status: ✅ Approved and Implemented Last Updated: October 2025