Estate Model Comparison: Legacy vs. New
Overview
This document compares the old (legacy) estate model from Vitec/Webmegler with the new estate model designed for Destinet. The new model is designed to work with Vitec data while providing a cleaner, more maintainable structure.
Key Problems with Legacy Model
1. System-Specific Field Names
The legacy model is tightly coupled to specific Norwegian real estate platforms:
// Legacy - Platform-specific fields
public string FinnEiendomstype { get; set; }
public string FinnOppdragstype { get; set; }
public string FinnEierformBygninger { get; set; }
public string TindeOppdragstype { get; set; }
public string TindeEiendomstype { get; set; }
public string KundenummerZett { get; set; }
Problems: - Not reusable with other systems - Duplicated data for each platform - Hard to maintain when platforms change - Cannot easily add new platforms
2. Mixed Concerns
Property data is mixed with presentation URLs and department information:
// Legacy - Everything in one flat class
public string Adresse { get; set; } // Property data
public string UrlNettversjon { get; set; } // Presentation URL
public string AvdelingNavn { get; set; } // Department data
public string AvdelingFagansvarligNavn { get; set; } // Agent data
public bool Markedsforingsklart { get; set; } // System state
Problems: - Difficult to understand what belongs where - Cannot easily update just property data - Hard to test individual components - Violates Single Responsibility Principle
3. Bloated Image Storage
Each image has 5 different URLs and 5 different filenames:
// Legacy - 10 properties per image!
public string UrlThumbnail { get; set; }
public string ThumbnailFil { get; set; }
public string UrlLiteBilde { get; set; }
public string LiteBildeFil { get; set; }
public string UrlStandardBilde { get; set; }
public string StandardBildeFil { get; set; }
public string UrlStorThumbnail { get; set; }
public string StorThumbnailFil { get; set; }
public string UrlOriginalBilde { get; set; }
public string OriginalBildeFil { get; set; }
Problems: - Extremely verbose - Difficult to work with programmatically - Wastes memory - Hard to add new image sizes
4. Norwegian Field Names
// Legacy - Norwegian names
public string Postnummer { get; set; }
public string Poststed { get; set; }
public string Byggeaar { get; set; }
public string Tomteareal { get; set; }
public decimal? Primaerrrom { get; set; }
public decimal? Bruksareal { get; set; }
Problems: - Not international - Harder for non-Norwegian developers - Inconsistent with modern C# naming conventions - Cannot be easily used in other countries
5. Unclear Data Types
// Legacy - Using strings for structured data
public string Oppvarming { get; set; } // Should be enum or object
public string TypeOppdrag { get; set; } // Should be enum
public string TypeEiendomstyper { get; set; } // Should be enum
Problems: - No type safety - Easy to make typos - No IntelliSense help - No validation
6. Flat Structure
Everything is at the root level - over 100+ properties:
public class LegacyEneiendom
{
// 120+ properties all at the same level
public string Adresse { get; set; }
public string Postnummer { get; set; }
public string Poststed { get; set; }
public string Kommune { get; set; }
public string Fylke { get; set; }
public string Bydel { get; set; }
public string Land { get; set; }
// ... 100+ more ...
}
Problems: - Overwhelming for developers - No logical grouping - Hard to find what you need - Difficult to validate
New Model Solutions
1. Clean Structure with Logical Grouping
// New - Organized into logical sections
public class Estate
{
// Core Identification
public string Heading { get; set; }
public string EstateId { get; set; }
public string AssignmentNum { get; set; }
public int DepartmentId { get; set; }
public DateTime ChangedDate { get; set; }
// Brokers
public List<BrokerWithRole> BrokersIdWithRoles { get; set; }
// Classification (int enums from Vitec)
public int EstateBaseType { get; set; }
public int AssignmentTypeGroup { get; set; }
public int Ownership { get; set; }
// Status
public int Status { get; set; }
public DateTime? SoldDate { get; set; }
public DateTime? ExpireDate { get; set; }
// Location (grouped in separate classes)
public Address Address { get; set; }
public GeoCoordinates GeoCoordinates { get; set; }
public List<Matrikkel> Matrikkel { get; set; }
// Property Details (direct properties + complex objects)
public int? NoOfRooms { get; set; }
public int? NoOfBedRooms { get; set; }
public int? NoOfBathRooms { get; set; }
public int? Floor { get; set; }
public int? ConstructionYear { get; set; }
public Plot Plot { get; set; }
public EstateSize EstateSize { get; set; }
// Energy
public EnergyRating EnergyRating { get; set; }
// Pricing
public EstatePrice EstatePrice { get; set; }
// Ownership (for cooperatives)
public PartOwnership PartOwnership { get; set; }
// Facilities
public List<string> EstateFacilities { get; set; }
public List<int> Facilities { get; set; }
// Showings
public List<Showing> Showings { get; set; }
public string ShowingNote { get; set; }
// Media & Links (populated from separate endpoints)
public ExternalPlatformUrls ExternalPlatformUrls { get; set; }
public List<PropertyLink> Links { get; set; }
public List<Image> Images { get; set; }
public List<PropertyDocument> Documents { get; set; }
// Description
public List<DescriptionSection> DescriptionSections { get; set; }
// Project Reference
public string ProjectId { get; set; }
// System Metadata
public string Origin { get; set; }
public Dictionary<string, object> CustomData { get; set; }
}
Benefits: - Clear sections with comments - Related properties grouped together - Easy to find what you need - More maintainable - Follows SOLID principles
2. Separation of Concerns
Instead of embedding department and employee data, the new model uses references:
// New - References via IDs (NOT embedded objects)
public int DepartmentId { get; set; } // → Department collection
public List<BrokerWithRole> BrokersIdWithRoles { get; set; } // → Employee collection
// BrokerWithRole contains:
public class BrokerWithRole
{
public string EmployeeId { get; set; } // Reference to Employee
public int BrokerRole { get; set; } // Role enum (primary, secondary, etc.)
}
Benefits: - Department and Employee are separate master entities - Update an employee's email once → reflects on all estates automatically - No duplicated data across 150+ estates - Normalized architecture - Can test each component separately
3. Clean Image Model
// New - Simple, shared image class
public class Image
{
public string Id { get; set; }
public string OriginalUrl { get; set; }
public string Filename { get; set; }
public string FileExtension { get; set; }
public string ExternalProviderUrl { get; set; }
public string Caption { get; set; }
public string AltText { get; set; }
public string Category { get; set; } // "Facade", "Interior", "Kitchen"
public int Order { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public DateTime LastModifiedOrigin { get; set; }
public DateTime LastModifiedLocal { get; set; }
}
Benefits:
- Clean, readable structure (12 properties vs. 10+ URLs per image in legacy)
- Shared across all entities (Estate, Project, Department, Employee)
- Flexible Category field adapts to context
- Contains all necessary metadata in one place
- Fetched from separate API endpoint and populated into list
4. International English Names
// New - International English
public string EstateId { get; set; } // vs. legacy: Id
public string AssignmentNum { get; set; } // vs. legacy: Oppdragsnummer
public int? NoOfRooms { get; set; } // vs. legacy: AntallRom
public int? ConstructionYear { get; set; } // vs. legacy: Byggeaar
// Address class
public string Street { get; set; } // vs. legacy: Adresse
public string ZipCode { get; set; } // vs. legacy: Postnummer
public string City { get; set; } // vs. legacy: Poststed
public string Municipality { get; set; } // vs. legacy: Kommune
public string County { get; set; } // vs. legacy: Fylke
// EstateSize class
public decimal? PrimaryRoomArea { get; set; } // vs. legacy: Primaerrrom
public decimal? UsableArea { get; set; } // vs. legacy: Bruksareal
public decimal? PlotArea { get; set; } // vs. legacy: Tomteareal
Benefits: - International standard - Easy for any developer to understand - Follows C# naming conventions - Consistent naming across all models
5. Proper Data Types with Enum Values
// New - Integer enums from source system (Vitec)
public int EstateBaseType { get; set; } // 0=NotSet, 1=Detached, 2=Leisure, etc.
public int AssignmentTypeGroup { get; set; } // 0=NotSet, 1=Sale, 2=Rent, etc.
public int Ownership { get; set; } // 0=Owned, 1=Cooperative, 2=Stock, etc.
public int Status { get; set; } // 0=Request, 1=Preparation, 2=ForSale, etc.
// System metadata for platform-specific data
public string Origin { get; set; } // "Vitec"
public Dictionary<string, object> CustomData { get; set; }
Benefits:
- Preserves exact source data (Vitec enum values)
- No conversion overhead
- Type safety with integers
- Can use enum mapping tables to convert to human-readable strings
- System-specific data stored separately in CustomData
6. Organized Complex Objects
Instead of flat structure, related data is grouped:
// Address - All location data together
public class Address
{
public string Street { get; set; }
public string PublicApartmentNumber { get; set; }
public string LocalAreaName { get; set; }
public string ZipCode { get; set; }
public string City { get; set; }
public string Municipality { get; set; }
public string County { get; set; }
public string Country { get; set; }
public string CountryCode { get; set; }
}
// EstateSize - All area measurements together
public class EstateSize
{
public decimal? PrimaryRoomArea { get; set; }
public decimal? GrossArea { get; set; }
public decimal? UsableArea { get; set; }
// ... other area measurements
}
// EstatePrice - All pricing information together
public class EstatePrice
{
public decimal? PriceSuggestion { get; set; }
public decimal? SoldPrice { get; set; }
public decimal? CollectiveDebt { get; set; }
public RentInfo Rent { get; set; }
public decimal? PurchaseCostsAmount { get; set; }
public decimal? TotalPrice { get; set; }
// ... other price-related fields
}
// PartOwnership - Cooperative/condominium details
public class PartOwnership
{
public string PartName { get; set; }
public string PartOrgNumber { get; set; }
public decimal? Deposit { get; set; }
public decimal? ShareJointDebtYear { get; set; }
// ... other cooperative fields
}
Benefits: - Related properties grouped together - Easy to find what you need - Can pass around logical units (e.g., just Address) - More maintainable and testable
7. Multi-Endpoint Data Population
The new model is designed to be populated from multiple API endpoints:
// Fetch from multiple endpoints and combine
var estate = FetchEstateBase(estateId); // Base data
estate.Images = FetchEstateImages(estateId); // Separate endpoint
estate.Documents = FetchEstateDocuments(estateId); // Separate endpoint
estate.Links = FetchEstateLinks(estateId); // Separate endpoint
estate.Showings = FetchEstateShowings(estateId); // Separate endpoint
Benefits: - Clean separation of data sources - Estate object serves as complete, denormalized view once populated - Can fetch only what you need - Matches Vitec API architecture
Side-by-Side Comparison
Address Information
| Legacy | New |
|---|---|
| 7 flat properties | 1 Address object |
Adresse, Postnummer, Poststed, Kommune, Fylke, Bydel, Land |
Street, ZipCode, City, Municipality, County, LocalAreaName, Country |
| Norwegian names | English names |
| No structure | Clean Address class |
| Mixed with other data | Separate shared class |
Images
| Legacy | New |
|---|---|
| 10 properties per image | 1 Image object with 12 properties |
| 5 URLs + 5 filenames per size | Single OriginalUrl + metadata |
| Confusing naming | Clear property names |
| Hard to iterate | Easy List<Image> |
| Estate-specific | Shared across all entities |
| Embedded in estate data | Fetched from separate endpoint |
Property Types
| Legacy | New |
|---|---|
| Multiple string fields per platform | Integer enum values |
FinnEiendomstype, TindeEiendomstype, TypeEiendomstyper |
EstateBaseType = 1 (Vitec enum) |
| Platform-specific strings | Source system enum values |
| Duplicated data | Single source of truth |
| No validation | Type-safe integers |
Department/Employee Info
| Legacy | New |
|---|---|
| 30+ prefixed flat properties embedded | References via IDs |
AvdelingNavn, AvdelingFagansvarligNavn, etc. |
DepartmentId, BrokersIdWithRoles |
| Duplicated on every estate | Stored once, referenced many times |
| Mixed with property data | Normalized architecture |
| Update employee email = update 150+ estates | Update employee email = update 1 employee record |
Pricing
| Legacy | New |
|---|---|
| 20+ flat price properties | 1 EstatePrice object |
| Mixed throughout the class | All pricing data grouped |
Prisantydning, Fellesgjeld, Omkostninger, etc. |
PriceSuggestion, CollectiveDebt, PurchaseCostsAmount |
| Norwegian names | English names |
| No structure | EstatePrice class with RentInfo subobject |
Area Measurements
| Legacy | New |
|---|---|
| 10+ flat area properties | 1 EstateSize object |
Primaerrrom, Bruksareal, BRA, BRAE, etc. |
PrimaryRoomArea, UsableArea, GrossArea, etc. |
| Norwegian abbreviations | Clear English names |
| Scattered in class | Grouped in EstateSize class |
Migration Path
Step 1: Map Legacy to New
Create a mapper that converts LegacyEneiendom → Estate:
public class VitecEstateMapper
{
public Estate MapToEstate(LegacyEneiendom legacy)
{
return new Estate
{
EstateId = legacy.Id.ToString(),
AssignmentNum = legacy.Oppdragsnummer.ToString(),
Heading = legacy.Tittel,
DepartmentId = legacy.AvdelingId,
ChangedDate = legacy.EndretDato,
// Classification - convert from strings to Vitec enum values
EstateBaseType = MapEstateBaseType(legacy.TypeEiendomstyper),
Ownership = MapOwnership(legacy.Eierform),
Status = MapStatus(legacy.Status),
// Location
Address = new Address
{
Street = legacy.Adresse,
ZipCode = legacy.Postnummer,
City = legacy.Poststed,
Municipality = legacy.Kommune,
County = legacy.Fylke,
Country = legacy.Land
},
GeoCoordinates = new GeoCoordinates
{
Latitude = legacy.Latitude,
Longitude = legacy.Longitude
},
// Property Details
NoOfRooms = legacy.AntallRom,
NoOfBedRooms = legacy.AntallSoverom,
ConstructionYear = ParseYear(legacy.Byggeaar),
// Area measurements
EstateSize = new EstateSize
{
PrimaryRoomArea = legacy.Primaerrrom,
UsableArea = legacy.Bruksareal,
GrossArea = legacy.BRA
},
// Pricing
EstatePrice = new EstatePrice
{
PriceSuggestion = legacy.Prisantydning,
CollectiveDebt = legacy.Fellesgjeld
},
// Origin tracking
Origin = "Vitec",
// System metadata for truly Vitec-specific data
CustomData = new Dictionary<string, object>
{
["finn_orderno"] = legacy.FinnOrderno,
["webmegler_database_id"] = legacy.Databasenummer
}
};
}
private int MapEstateBaseType(string legacyType)
{
// Convert legacy string to Vitec enum value
return legacyType switch
{
"Bolig" => 1, // Detached
"Fritid" => 2, // Leisure
"Næring" => 3, // Business
_ => 0 // NotSet
};
}
}
Step 2: Handle Images Separately
// Legacy had images embedded
// New model fetches from separate endpoint
estate.Images = await FetchImagesFromVitecApi(estateId);
Step 3: Map Brokers/Employees
// Legacy had embedded agent data
// New model uses references
estate.BrokersIdWithRoles = new List<BrokerWithRole>
{
new BrokerWithRole
{
EmployeeId = legacy.FagansvarligId.ToString(),
BrokerRole = 1 // Primary
}
};
Step 4: Gradually Migrate
- Keep both models during transition
- Use new model for new features
- Migrate existing features incrementally
- Remove legacy model when fully migrated
Benefits Summary
The new Estate model provides:
✅ Clean structure - organized, logical grouping with sections ✅ Separation of concerns - references instead of embedded data ✅ International - English naming, usable globally ✅ Type safety - integer enums from source system ✅ Maintainability - easy to understand and extend ✅ Best practices - follows SOLID principles ✅ Shared classes - Image, Address, etc. reused across all models ✅ Multi-endpoint support - designed for modern API patterns ✅ System metadata - flexible storage for platform-specific data
When to Use Each Model
Legacy Model: - Documentation purposes only - Migration reference - Understanding the source XML structure - DO NOT USE for new development
New Estate Model: - All new development - Working with Vitec data - Building new features - Creating APIs and services
For all new development, use the new Estate model.