Configure PostgreSQL and Entity Framework Core in Bookify
Bookify uses PostgreSQL as its primary database with Entity Framework Core as the ORM. This guide covers database configuration, migrations, seeding, and schema overview.
CREATE TABLE apartments ( id uuid PRIMARY KEY, name varchar(200) NOT NULL, description varchar(2000) NOT NULL, address_country text NOT NULL, address_state text NOT NULL, address_city text NOT NULL, address_zip_code text NOT NULL, address_street text NOT NULL, price_amount numeric NOT NULL, price_currency text NOT NULL, cleaning_fee_amount numeric NOT NULL, cleaning_fee_currency text NOT NULL, last_booked_on_utc timestamptz, amenities integer[], xmin xid -- Optimistic concurrency token);
users
Stores user accounts.
CREATE TABLE users ( id uuid PRIMARY KEY, first_name varchar(200) NOT NULL, last_name varchar(200) NOT NULL, email varchar(400) NOT NULL);CREATE UNIQUE INDEX ix_users_email ON users(email);
bookings
Stores apartment reservations.
CREATE TABLE bookings ( id uuid PRIMARY KEY, apartment_id uuid NOT NULL REFERENCES apartments(id), user_id uuid NOT NULL REFERENCES users(id), duration_start date NOT NULL, duration_end date NOT NULL, price_for_period_amount numeric NOT NULL, price_for_period_currency text NOT NULL, cleaning_fee_amount numeric NOT NULL, cleaning_fee_currency text NOT NULL, amenities_up_charge_amount numeric NOT NULL, amenities_up_charge_currency text NOT NULL, total_price_amount numeric NOT NULL, total_price_currency text NOT NULL, status integer NOT NULL, created_on_utc timestamptz NOT NULL, confirmed_on_utc timestamptz, rejected_on_utc timestamptz, completed_on_utc timestamptz, cancelled_on_utc timestamptz);CREATE INDEX ix_bookings_apartment_id ON bookings(apartment_id);CREATE INDEX ix_bookings_user_id ON bookings(user_id);
reviews
Stores booking reviews.
CREATE TABLE reviews ( id uuid PRIMARY KEY, apartment_id uuid NOT NULL REFERENCES apartments(id), booking_id uuid NOT NULL REFERENCES bookings(id), user_id uuid NOT NULL REFERENCES users(id), rating integer NOT NULL, comment varchar(200) NOT NULL, created_on_utc timestamptz NOT NULL);CREATE INDEX ix_reviews_apartment_id ON reviews(apartment_id);CREATE INDEX ix_reviews_booking_id ON reviews(booking_id);CREATE INDEX ix_reviews_user_id ON reviews(user_id);
For read-heavy operations, use Dapper with ISqlConnectionFactory:
public class SearchApartmentsQueryHandler{ private readonly ISqlConnectionFactory _sqlConnectionFactory; public async Task<IReadOnlyList<ApartmentResponse>> Handle( SearchApartmentsQuery request, CancellationToken cancellationToken) { using var connection = _sqlConnectionFactory.CreateConnection(); const string sql = """ SELECT id, name, description, price_amount, price_currency, address_country, address_city FROM apartments WHERE NOT EXISTS ( SELECT 1 FROM bookings WHERE bookings.apartment_id = apartments.id AND bookings.duration_start <= @EndDate AND bookings.duration_end >= @StartDate AND bookings.status = @Status ) """; var apartments = await connection.QueryAsync<ApartmentResponse>( sql, new { request.StartDate, request.EndDate, Status = BookingStatus.Reserved }); return apartments.ToList(); }}