Introduction
This was my first professional project as a software developer, so I was determined to approach the development process properly. Since our team was small, we followed an Agile methodology for speed and flexibility. After years of working on personal projects, it was exciting to finally experience a structured development cycle. I spent the first few days researching and testing different UI frameworks, and eventually settled on Windows Forms. Although it’s dated and lacks the capabilities of modern frameworks, it was the right choice for building an internal company tool, where functionality and speed of delivery mattered more than visual style. For the database, I worked with Entity Framework Core. It was a new technology for me, but I was eager to learn it because of the convenience it offers for data management and migrations. The learning curve was worth it, and I came out of the project much more confident in handling real-world database challenges. Delivering the finished product was a huge milestone. It validated my ability to take a project from design to deployment, lead a small team, and adapt to new technologies. Following this project, I was offered a full-time position as a System Engineer.
Entity Framework Core & Database Design
When building the application, I wanted a clean separation between the business logic/UI layer and the data access layer. For this, I created a dedicated class library project that contained all Entity Framework Core models, the DbContext, and migrations. The main Windows Forms application only references this library, which keeps the architecture modular and easier to maintain.
The backend was developed entirely using EF Core, which provides its own query language, LINQ. LINQ queries are translated into SQL statements for the database provider of your choice, making it simple to work with different databases without needing deep knowledge of their specific query syntax. The entity classes and their relationships were written using EF Core conventions and attributes, and one particularly interesting example.
namespace EFDataAccessLibrary.Models
{
[PrimaryKey(nameof(BuildingNumber), nameof(ApartmentNumber))]
public class Apartment
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BuildingNumber { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int ApartmentNumber { get; set; }
[Required]
[MaxLength(50)]
public string TenantName { get; set; }
[Required]
[Column(TypeName = "date")]
public DateTime ContractStartDate { get; set; }
[Required]
[Column(TypeName = "date")]
public DateTime ContractEndDate { get; set; }
public bool Ejari { get; set; }
[Required]
[MaxLength(50)]
public int Deposit { get; set; }
//Amount
[Required]
[MaxLength(50)]
public int Amount { get; set; }
public List<ApartmentService> ApartmentServices { get; set; } = new List<ApartmentService>();
public List<ApartmentCheque> ApartmentCheques { get; set; } = new List<ApartmentCheque>();
public ApartmentPPM? PPM { get; set; }
}
}
The code above defines the Apartment
entity, which stores information about an apartment and its tenant. You can see in the last three properties that
it has relationships with other entities such as ApartmentService
, ApartmentCheque
, and ApartmentPPM
.
Another interesting aspect of this class is that its primary key is a composite key, consisting of both the apartment number and building number,
which ensures uniqueness across the entire property portfolio.
Alerts Functionality
The application also notifies you of any pending or future tasks that need attention. On the main page, there is a dashboard that displays currently pending alerts, while the calendar that highlights the dates of due or future tasks.
This is performed using a function that searches relevant records and compares their dates againts today's (or user-selected) date using LINQ queries like so:
public List<Alert> GetTodaysAlerts(DateTime targetDate)
{
var today = DateTime.Today;
var tenancyAlerts = Apartments2
.Where(a => a.ContractEndDate == targetDate)
.Select(a => new Alert
{
BuildingNumber = a.BuildingNumber,
About = a.ApartmentNumber.ToString(),
EventDate = a.ContractEndDate,
Description = "Tenancy Deadline",
EventTime = TimeSpan.Zero
});
...
return tenancyAlerts
.Union(chequeAlerts)
.Union(serviceAlerts)
.Union(Q1PPMAlerts)
.Union(Q2PPMAlerts)
.Union(Q3PPMAlerts)
.Union(Q4PPMAlerts)
.Union(contractDueAlerts)
.Union(PPMAlerts)
.AsEnumerable()
.OrderByDescending(a => a.EventDate)
.ThenBy(a => a.EventTime)
.ToList();
}
At the end of the function, all the collected alerts are merged using .Union()
. Finally, I optimized the remaining ordering functions by using .AsEnumerable()
which forces the query to transition from server-side execution to client-side, allowing the use of OrderBy()
with types not natively supported by SQL (such as
TimeSpan
).