diff --git a/Dania.Phonebook/Assets/1.png b/Dania.Phonebook/Assets/1.png new file mode 100644 index 00000000..4e8be0de Binary files /dev/null and b/Dania.Phonebook/Assets/1.png differ diff --git a/Dania.Phonebook/Assets/2.png b/Dania.Phonebook/Assets/2.png new file mode 100644 index 00000000..7c58368b Binary files /dev/null and b/Dania.Phonebook/Assets/2.png differ diff --git a/Dania.Phonebook/Assets/3.png b/Dania.Phonebook/Assets/3.png new file mode 100644 index 00000000..64649d82 Binary files /dev/null and b/Dania.Phonebook/Assets/3.png differ diff --git a/Dania.Phonebook/Assets/4.png b/Dania.Phonebook/Assets/4.png new file mode 100644 index 00000000..c3d5f47b Binary files /dev/null and b/Dania.Phonebook/Assets/4.png differ diff --git a/Dania.Phonebook/Assets/5.png b/Dania.Phonebook/Assets/5.png new file mode 100644 index 00000000..6756a64d Binary files /dev/null and b/Dania.Phonebook/Assets/5.png differ diff --git a/Dania.Phonebook/Phonebook/Controller/ContactController.cs b/Dania.Phonebook/Phonebook/Controller/ContactController.cs new file mode 100644 index 00000000..97d89e97 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Controller/ContactController.cs @@ -0,0 +1,289 @@ +using Phonebook.Model; + +namespace Phonebook.Controller; + +internal class ContactController +{ + static DatabaseManager dbmanager = new DatabaseManager(); + static Helpers helpers = new Helpers(); + static List contacts = new List(); + + internal static void ViewContacts() + { + contacts.Clear(); + + Console.Clear(); + Console.WriteLine("---View all Contacts---"); + + contacts = dbmanager.ViewContacts(); + + if (contacts.Count == 0) + { + Console.WriteLine("No contacts available"); + } + else + { + foreach (var contact in contacts) + { + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + } + } + + Console.WriteLine("Press enter to go back to the main menu."); + Console.ReadLine(); + } + + internal static void NewContact() + { + Console.Clear(); + Console.WriteLine("---Add New Contact---"); + Console.WriteLine("Insert new contact name (Insert 0 to return to main menu):"); + string name = helpers.CheckName(); + + if (name == "0") return; + + Console.WriteLine("Insert new contact phone number (ie.+15166024355)(Insert 0 to return to main menu): "); + string number = helpers.CheckPhoneNumber(); + + if(number == "0") return; + + Console.WriteLine("Insert new contact email address (Insert 0 to return to main menu):"); + string email = helpers.CheckEmail(); + + if(email == "0") return; + + if(dbmanager.AddContact(name, number, email)) + Console.WriteLine("\nNew Contact has been added in the Phonebook!"); + else + Console.WriteLine("\nContact added unsuccessful"); + + Console.WriteLine("Would you like to add another contact?: (y/n)"); + string input = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(input) && input.ToLower() == "y") + NewContact(); + } + + internal static void DeleteContact() + { + contacts.Clear(); + + Console.Clear(); + Console.WriteLine("---Delete Contact---"); + + Console.WriteLine("Insert the contact name (Insert 0 to return to main menu): "); + string name = helpers.CheckName(); + + if (name == "0") return; + + contacts = dbmanager.FindContact(name); + + if (contacts.Count == 0) + { + Console.WriteLine("Contact not found\n"); + } + else + { + Console.Clear(); + Console.WriteLine("---Delete Contact---"); + Console.WriteLine($"{contacts.Count} found\n"); + + foreach (var contact in contacts) + { + Console.WriteLine($"ID: {contact.Id}"); + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + } + + Console.WriteLine("Insert their ID to delete their contact (Insert 0 to return to main menu): "); + int id = helpers.CheckNumber(); + + if (id == 0) return; + + List foundContact = new List(); + foundContact = dbmanager.FindContactByIdAndName(id, name); + + + if (foundContact.Count == 0) + { + Console.WriteLine("ID doesn't match to any of the contact name."); + } + else + { + Console.Clear(); + Console.WriteLine("---Delete Contact---"); + foreach (var contact in foundContact) + { + Console.WriteLine($"ID: {contact.Id}"); + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + } + + Console.WriteLine("Are you sure you want to delete this contact? (y/n)"); + string confirmation = Console.ReadLine(); + + if (!string.IsNullOrWhiteSpace(confirmation) && confirmation.Trim().ToLower() == "y") + { + if (dbmanager.DeleteById(id)) + Console.WriteLine("Contact deleted sucessfully!"); + else + Console.WriteLine("Delete contact unsuccessful"); + } + else + Console.WriteLine("Delete contact cancelled"); + } + } + + Console.WriteLine("\nWould you like to delete another contact?: (Y/N)"); + string input = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(input) && input.ToLower() == "y") + DeleteContact(); + + } + + internal static void UpdateContact() + { + Console.Clear(); + Console.WriteLine("---Update Contact---"); + + Console.WriteLine("Insert the contact name (Insert 0 to return to main menu): "); + string name = helpers.CheckName(); + + if (name == "0") return; + + contacts = dbmanager.FindContact(name); + + if (contacts.Count == 0) + { + Console.WriteLine("Contact not found\n"); + } + else + { + Console.Clear(); + Console.WriteLine("---Update Contact---"); + Console.WriteLine($"{contacts.Count} found\n"); + + foreach (var contact in contacts) + { + Console.WriteLine($"ID: {contact.Id}"); + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + } + + Console.WriteLine("Insert their ID to update their contact (Insert 0 to return to main menu): "); + int id = helpers.CheckNumber(); + + if (id == 0) return; + + List foundContact = new List(); + foundContact = dbmanager.FindContactByIdAndName(id, name); + + if (foundContact.Count == 0) + { + Console.WriteLine("ID doesn't match to any of the contact name."); + } + else + { + var contact = foundContact[0]; + bool isCompleted = false; + + while (!isCompleted) + { + Console.Clear(); + Console.WriteLine("---Update Contact---"); + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + Console.WriteLine("1 - Update name."); + Console.WriteLine("2 - Update phone number."); + Console.WriteLine("3 - Update email address."); + Console.WriteLine("0 - Complete changes"); + + string updateInput = Console.ReadLine(); + + switch (updateInput) + { + case "0": + isCompleted = true; + break; + case "1": + Console.WriteLine("\nInsert new contact name (Insert 0 to return to keep): "); + string newName = helpers.CheckName(); + if(newName != "0") + contact.Name = newName; + break; + case "2": + Console.WriteLine("\nInsert new contact phone number (00-0000-0000) (Insert 0 to return to keep): "); + string number = helpers.CheckPhoneNumber(); + if (number != "0") + contact.PhoneNumber = number; + break; + case "3": + Console.WriteLine("Insert new contact email address (Insert 0 to return to keep):"); + string email = helpers.CheckEmail(); + if (email != "0") + contact.EmailAddress = email; + break; + } + + } + + if (dbmanager.UpdateById(id, contact.Name, contact.PhoneNumber, contact.EmailAddress)) + Console.WriteLine("Contact update sucessful!"); + else + Console.WriteLine("Contact update unsuccessful"); + + } + } + + Console.WriteLine("\nWould you like to update another contact?: (Y/N)"); + string input = Console.ReadLine(); + if (!string.IsNullOrWhiteSpace(input) && input.ToLower() == "y") + UpdateContact(); + } + + internal static void FindContact() + { + contacts.Clear(); + + Console.Clear(); + Console.WriteLine("---Find Contact---"); + Console.WriteLine("Insert the contact name: "); + string input = helpers.CheckName(); + + if (input == "0") return; + + contacts = dbmanager.FindContact(input); + + if (contacts.Count == 0) + { + Console.WriteLine("Contact not found\n"); + } + else + { + Console.Clear(); + Console.WriteLine("---Find Contact---"); + Console.WriteLine($"{contacts.Count} found\n"); + + foreach (var contact in contacts) + { + Console.WriteLine($"Name: {contact.Name}"); + Console.WriteLine($"Phone: {contact.PhoneNumber}"); + Console.WriteLine($"E-mail: {contact.EmailAddress}"); + Console.WriteLine(); + } + } + + Console.WriteLine("Press enter to go back to the main menu."); + Console.ReadLine(); + } +} diff --git a/Dania.Phonebook/Phonebook/DatabaseManager.cs b/Dania.Phonebook/Phonebook/DatabaseManager.cs new file mode 100644 index 00000000..b78e0f05 --- /dev/null +++ b/Dania.Phonebook/Phonebook/DatabaseManager.cs @@ -0,0 +1,128 @@ +using Microsoft.EntityFrameworkCore; +using Phonebook.Model; + +namespace Phonebook; + +internal class DatabaseManager +{ + internal List ViewContacts() + { + + try + { + using (var context = new PhonebookContext()) + { + var contacts = context.Contacts.ToList(); + + return contacts; + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Error] Could not load contacts: {ex.Message}"); + return new List(); + } + } + + internal bool AddContact(string name, string phoneNumber, string email) + { + try + { + using (var context = new PhonebookContext()) + { + var contact = new Contact() + { + Name = name, + PhoneNumber = phoneNumber, + EmailAddress = email + }; + + context.Add(contact); + context.SaveChanges(); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Unexpected Error] {ex.Message}"); + return false; + } + } + + internal List FindContact(string name) + { + try + { + using (var context = new PhonebookContext()) + { + var contact = context.Contacts.Where(c => c.Name.Contains(name)).OrderBy(n => n.Name); + return contact.ToList(); + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Error] Search failed: {ex.Message}"); + return new List(); + } + } + + internal List FindContactByIdAndName(int id, string name) + { + try + { + using (var context = new PhonebookContext()) + { + var contact = context.Contacts.Where(c => c.Id == id && c.Name.Contains(name)); + return contact.ToList(); + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Error] Find failed: {ex.Message}"); + return new List(); + } + } + + internal bool DeleteById(int id) + { + try + { + using (var context = new PhonebookContext()) + { + var rowsDeleted = context.Contacts.Where(c => c.Id == id).ExecuteDelete(); + return true; + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Error] Could not delete contact: {ex.Message}"); + return false; + } + + } + + internal bool UpdateById(int id, string name, string phoneNumber, string email) + { + try + { + using (var context = new PhonebookContext()) + { + context.Contacts.Where(c => c.Id == id).ExecuteUpdate(setter => + { + setter.SetProperty(c => c.Name, name); + setter.SetProperty(c => c.PhoneNumber, phoneNumber); + setter.SetProperty(c => c.EmailAddress, email); + + }); + + return true; + } + } + catch (Exception ex) + { + Console.WriteLine($"\n[Error] Could not update contact: {ex.Message}"); + return false; + } + + } +} diff --git a/Dania.Phonebook/Phonebook/Helpers.cs b/Dania.Phonebook/Phonebook/Helpers.cs new file mode 100644 index 00000000..77b2eecc --- /dev/null +++ b/Dania.Phonebook/Phonebook/Helpers.cs @@ -0,0 +1,105 @@ +using System.Text.RegularExpressions; + +namespace Phonebook; + +internal class Helpers +{ + const string PHONE_PATTERN = @"^\+?\d{1,4}?[-.\s]?\(?\d{1,3}?\)?[-.\s]?\d{1,4}[-.\s]?\d{1,4}[-.\s]?\d{1,9}$"; + const string EMAIL_PATTERN = @"^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-z]{2,}$"; + + internal string CheckName() + { + string name = ""; + bool isValid = false; + + do + { + name = Console.ReadLine(); + if (name == "0") + isValid = true; + + if (string.IsNullOrWhiteSpace(name)) + Console.WriteLine("Name cannot be empty. Try again: "); + else + isValid = true; + + } while (!isValid); + + return name; + } + + internal int CheckNumber() + { + int number = 0; + bool isValid = false; + do + { + string input = Console.ReadLine(); + if (input == "0") + { + number = 0; + isValid = true; + } + + if (string.IsNullOrWhiteSpace(input)) + Console.WriteLine("ID cannot be empty. Try again: "); + else + { + if(int.TryParse(input, out number)) + isValid = true; + else + Console.WriteLine($"ID is not a number.Try again: "); + + } + + }while (!isValid); + + return number; + + } + + internal string CheckPhoneNumber() + { + string number = ""; + bool isValid = false; + + do + { + number = Console.ReadLine(); + if(number == "0") + isValid = true; + + if (Regex.IsMatch(number, PHONE_PATTERN)) + isValid = true; + else + { + Console.WriteLine("Not a valid phone number. Try again: "); + } + } + while (!isValid); + + return number; + } + + + internal string CheckEmail() + { + string email = ""; + bool isValid = false; + + do + { + email = Console.ReadLine(); + if (email == "0") + isValid = true; + + if (Regex.IsMatch(email, EMAIL_PATTERN)) + isValid = true; + else + Console.WriteLine("Not a valid email. Try again: "); + } + while (!isValid); + + return email; + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.Designer.cs b/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.Designer.cs new file mode 100644 index 00000000..20093748 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.Designer.cs @@ -0,0 +1,52 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Phonebook; + +#nullable disable + +namespace Phonebook.Migrations +{ + [DbContext(typeof(PhonebookContext))] + [Migration("20260522034000_InitialPhonebookDB")] + partial class InitialPhonebookDB + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Phonebook.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EmailAddress") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.cs b/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.cs new file mode 100644 index 00000000..eea4bd9b --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260522034000_InitialPhonebookDB.cs @@ -0,0 +1,36 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Phonebook.Migrations +{ + /// + public partial class InitialPhonebookDB : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Contacts", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("SqlServer:Identity", "1, 1"), + Name = table.Column(type: "nvarchar(max)", nullable: false), + PhoneNumber = table.Column(type: "int", nullable: false), + EmailAddress = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Contacts", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Contacts"); + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.Designer.cs b/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.Designer.cs new file mode 100644 index 00000000..782c6b65 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.Designer.cs @@ -0,0 +1,54 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Phonebook; + +#nullable disable + +namespace Phonebook.Migrations +{ + [DbContext(typeof(PhonebookContext))] + [Migration("20260606060442_UpdateDataType")] + partial class UpdateDataType + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Phonebook.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.cs b/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.cs new file mode 100644 index 00000000..29f6029a --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260606060442_UpdateDataType.cs @@ -0,0 +1,50 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Phonebook.Migrations +{ + /// + public partial class UpdateDataType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PhoneNumber", + table: "Contacts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + + migrationBuilder.AlterColumn( + name: "EmailAddress", + table: "Contacts", + type: "nvarchar(max)", + nullable: false, + oldClrType: typeof(int), + oldType: "int"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "PhoneNumber", + table: "Contacts", + type: "int", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "EmailAddress", + table: "Contacts", + type: "int", + nullable: false, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.Designer.cs b/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.Designer.cs new file mode 100644 index 00000000..580e68a7 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.Designer.cs @@ -0,0 +1,91 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Phonebook; + +#nullable disable + +namespace Phonebook.Migrations +{ + [DbContext(typeof(PhonebookContext))] + [Migration("20260610093320_SeedInitialContacts")] + partial class SeedInitialContacts + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Phonebook.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + + b.HasData( + new + { + Id = 1, + EmailAddress = "johnDoe@gmail.com", + Name = "John Doe", + PhoneNumber = "110000111" + }, + new + { + Id = 2, + EmailAddress = "john.doe@gmail.com", + Name = "John Doe", + PhoneNumber = "114345111" + }, + new + { + Id = 3, + EmailAddress = "maryjane@gmail.com", + Name = "Mary Jane", + PhoneNumber = "110500631" + }, + new + { + Id = 4, + EmailAddress = "alicegreen@gmail.com", + Name = "Alice Green", + PhoneNumber = "120305141" + }, + new + { + Id = 5, + EmailAddress = "bobvance@gmail.com", + Name = "Bob Vance", + PhoneNumber = "120775991" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.cs b/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.cs new file mode 100644 index 00000000..c1622c55 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260610093320_SeedInitialContacts.cs @@ -0,0 +1,57 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Phonebook.Migrations +{ + /// + public partial class SeedInitialContacts : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.InsertData( + table: "Contacts", + columns: new[] { "Id", "EmailAddress", "Name", "PhoneNumber" }, + values: new object[,] + { + { 1, "johnDoe@gmail.com", "John Doe", "110000111" }, + { 2, "john.doe@gmail.com", "John Doe", "114345111" }, + { 3, "maryjane@gmail.com", "Mary Jane", "110500631" }, + { 4, "alicegreen@gmail.com", "Alice Green", "120305141" }, + { 5, "bobvance@gmail.com", "Bob Vance", "120775991" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + table: "Contacts", + keyColumn: "Id", + keyValue: 1); + + migrationBuilder.DeleteData( + table: "Contacts", + keyColumn: "Id", + keyValue: 2); + + migrationBuilder.DeleteData( + table: "Contacts", + keyColumn: "Id", + keyValue: 3); + + migrationBuilder.DeleteData( + table: "Contacts", + keyColumn: "Id", + keyValue: 4); + + migrationBuilder.DeleteData( + table: "Contacts", + keyColumn: "Id", + keyValue: 5); + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.Designer.cs b/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.Designer.cs new file mode 100644 index 00000000..88db56ab --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.Designer.cs @@ -0,0 +1,91 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Phonebook; + +#nullable disable + +namespace Phonebook.Migrations +{ + [DbContext(typeof(PhonebookContext))] + [Migration("20260611130136_UpdateSeedPhoneNumbers")] + partial class UpdateSeedPhoneNumbers + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Phonebook.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + + b.HasData( + new + { + Id = 1, + EmailAddress = "johnDoe@gmail.com", + Name = "John Doe", + PhoneNumber = "+18708291967" + }, + new + { + Id = 2, + EmailAddress = "john.doe@gmail.com", + Name = "John Doe", + PhoneNumber = "+447457095201" + }, + new + { + Id = 3, + EmailAddress = "maryjane@gmail.com", + Name = "Mary Jane", + PhoneNumber = "+61451146406" + }, + new + { + Id = 4, + EmailAddress = "alicegreen@gmail.com", + Name = "Alice Green", + PhoneNumber = "+13052428540" + }, + new + { + Id = 5, + EmailAddress = "bobvance@gmail.com", + Name = "Bob Vance", + PhoneNumber = "+447984372345" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.cs b/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.cs new file mode 100644 index 00000000..51e5754f --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/20260611130136_UpdateSeedPhoneNumbers.cs @@ -0,0 +1,88 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Phonebook.Migrations +{ + /// + public partial class UpdateSeedPhoneNumbers : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 1, + column: "PhoneNumber", + value: "+18708291967"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 2, + column: "PhoneNumber", + value: "+447457095201"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 3, + column: "PhoneNumber", + value: "+61451146406"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 4, + column: "PhoneNumber", + value: "+13052428540"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 5, + column: "PhoneNumber", + value: "+447984372345"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 1, + column: "PhoneNumber", + value: "110000111"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 2, + column: "PhoneNumber", + value: "114345111"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 3, + column: "PhoneNumber", + value: "110500631"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 4, + column: "PhoneNumber", + value: "120305141"); + + migrationBuilder.UpdateData( + table: "Contacts", + keyColumn: "Id", + keyValue: 5, + column: "PhoneNumber", + value: "120775991"); + } + } +} diff --git a/Dania.Phonebook/Phonebook/Migrations/PhonebookContextModelSnapshot.cs b/Dania.Phonebook/Phonebook/Migrations/PhonebookContextModelSnapshot.cs new file mode 100644 index 00000000..5e1b4275 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Migrations/PhonebookContextModelSnapshot.cs @@ -0,0 +1,88 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Phonebook; + +#nullable disable + +namespace Phonebook.Migrations +{ + [DbContext(typeof(PhonebookContext))] + partial class PhonebookContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.9") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Phonebook.Model.Contact", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id")); + + b.Property("EmailAddress") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Contacts"); + + b.HasData( + new + { + Id = 1, + EmailAddress = "johnDoe@gmail.com", + Name = "John Doe", + PhoneNumber = "+18708291967" + }, + new + { + Id = 2, + EmailAddress = "john.doe@gmail.com", + Name = "John Doe", + PhoneNumber = "+447457095201" + }, + new + { + Id = 3, + EmailAddress = "maryjane@gmail.com", + Name = "Mary Jane", + PhoneNumber = "+61451146406" + }, + new + { + Id = 4, + EmailAddress = "alicegreen@gmail.com", + Name = "Alice Green", + PhoneNumber = "+13052428540" + }, + new + { + Id = 5, + EmailAddress = "bobvance@gmail.com", + Name = "Bob Vance", + PhoneNumber = "+447984372345" + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Dania.Phonebook/Phonebook/Model/Contact.cs b/Dania.Phonebook/Phonebook/Model/Contact.cs new file mode 100644 index 00000000..948f1efe --- /dev/null +++ b/Dania.Phonebook/Phonebook/Model/Contact.cs @@ -0,0 +1,9 @@ +namespace Phonebook.Model; + +internal class Contact +{ + public int Id { get; set; } + public string Name { get; set; } + public string PhoneNumber { get; set; } + public string EmailAddress { get; set; } +} diff --git a/Dania.Phonebook/Phonebook/Phonebook.csproj b/Dania.Phonebook/Phonebook/Phonebook.csproj new file mode 100644 index 00000000..4fd875a7 --- /dev/null +++ b/Dania.Phonebook/Phonebook/Phonebook.csproj @@ -0,0 +1,29 @@ + + + + Exe + net10.0 + enable + enable + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + PreserveNewest + + + + diff --git a/Dania.Phonebook/Phonebook/Phonebook.slnx b/Dania.Phonebook/Phonebook/Phonebook.slnx new file mode 100644 index 00000000..0b5434ac --- /dev/null +++ b/Dania.Phonebook/Phonebook/Phonebook.slnx @@ -0,0 +1,3 @@ + + + diff --git a/Dania.Phonebook/Phonebook/PhonebookContext.cs b/Dania.Phonebook/Phonebook/PhonebookContext.cs new file mode 100644 index 00000000..5661c32f --- /dev/null +++ b/Dania.Phonebook/Phonebook/PhonebookContext.cs @@ -0,0 +1,31 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Phonebook.Model; + +namespace Phonebook; + +internal class PhonebookContext : DbContext +{ + public DbSet Contacts { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + IConfiguration config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); + string connectionString = config.GetConnectionString("DefaultConnection"); + + optionsBuilder.UseSqlServer(connectionString); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity().HasData( + new Contact { Id = 1, Name = "John Doe", PhoneNumber = "+18708291967", EmailAddress = "johnDoe@gmail.com"}, + new Contact { Id = 2, Name = "John Doe", PhoneNumber = "+447457095201", EmailAddress = "john.doe@gmail.com"}, + new Contact { Id = 3, Name = "Mary Jane", PhoneNumber = "+61451146406", EmailAddress = "maryjane@gmail.com"}, + new Contact { Id = 4, Name = "Alice Green", PhoneNumber = "+13052428540", EmailAddress = "alicegreen@gmail.com"}, + new Contact { Id = 5, Name = "Bob Vance", PhoneNumber = "+447984372345", EmailAddress = "bobvance@gmail.com" } + ); + } +} diff --git a/Dania.Phonebook/Phonebook/Program.cs b/Dania.Phonebook/Phonebook/Program.cs new file mode 100644 index 00000000..76fd4c3c --- /dev/null +++ b/Dania.Phonebook/Phonebook/Program.cs @@ -0,0 +1,4 @@ +using Phonebook; + +UserInterface userInterface = new UserInterface(); +userInterface.MainMenu(); diff --git a/Dania.Phonebook/Phonebook/UserInterface.cs b/Dania.Phonebook/Phonebook/UserInterface.cs new file mode 100644 index 00000000..32a9f9b1 --- /dev/null +++ b/Dania.Phonebook/Phonebook/UserInterface.cs @@ -0,0 +1,48 @@ +using Phonebook.Controller; + +namespace Phonebook; + +internal class UserInterface +{ + internal void MainMenu() + { + bool isCloseApp = false; + + while (!isCloseApp) + { + Console.Clear(); + Console.WriteLine("---Welcome to Phonebook---"); + Console.WriteLine("1 - View all contacts"); + Console.WriteLine("2 - Add new contact"); + Console.WriteLine("3 - Delete contact"); + Console.WriteLine("4 - Update contact"); + Console.WriteLine("5 - Find contact"); + Console.WriteLine("0 - Exit"); + Console.WriteLine("Please select an option: "); + + string input = Console.ReadLine(); + + switch (input) + { + case "0": + isCloseApp = true; + break; + case "1": + ContactController.ViewContacts(); + break; + case "2": + ContactController.NewContact(); + break; + case "3": + ContactController.DeleteContact(); + break; + case "4": + ContactController.UpdateContact(); + break; + case "5": + ContactController.FindContact(); + break; + } + } + } +} diff --git a/Dania.Phonebook/Phonebook/appsettings.json b/Dania.Phonebook/Phonebook/appsettings.json new file mode 100644 index 00000000..d0ba8df3 --- /dev/null +++ b/Dania.Phonebook/Phonebook/appsettings.json @@ -0,0 +1,5 @@ +{ + "ConnectionStrings": { + "DefaultConnection": "Server=(localdb)\\MSSQLLocalDB;Database=PhonebookDB;Trusted_Connection=True;" + } +} \ No newline at end of file diff --git a/Dania.Phonebook/README.md b/Dania.Phonebook/README.md new file mode 100644 index 00000000..83705378 --- /dev/null +++ b/Dania.Phonebook/README.md @@ -0,0 +1,40 @@ +# CSharpAcademy_Phonebook +Phonebook is the third olive green belt project from the C# Academy. The project introduces a popular ORM called Entity Framework onto the project. I programmed using C# with packages such as Entity Framework Core and SQL Server Management for the database with Visual Studio 2026. I also use Google Gemini to explain certain things or when I get stuck. + +## Requirements +- Use Entity Framework. ADO.NET, Dapper and any other ORM aren't allowed. +- Code should contain a base Contact class with at least name, email and phone number properties. +- Validate e-mails and phone numbers and let the user know what formats are expected. +- Handle errors so the app doesn't crash unexpectedly in case EF or the database have problems. +- Seed data using Entity Framework so the user has some contacts to start with. + +## Features +- Database using Entity Framework. + +![Image](Assets/1.png) + +- CRUD functions: + - Users insert their contact name, phone number and e-mail. + - Users can view all contact and find contact by their name, + - Users can delete and update contact using a combination of Name and ID to prevent conflicts from duplicate names. + - Phone number and E-mail are validated using Regex pattern. +![Image](Assets/2.png) + +## Challenges +- Learning about Entity Framework itself. This one is a bit different than other ORMS as you have to start coding the model, which are the entity (table) and then the database created. The setup is more elaborate than the other ORMs starting with making a Context class for configuration and a bridge between the project and the database, and create a migration through Package Manager Console to create the whole database. For CRUD operations, EF Core execute SQL commands for you, but instead of SQL, I have to learn LINQ commands to Read, Update and Delete. +![Image](Assets/3.png) +DBContext class +![Image](Assets/4.png) +An example of CRUD command using LINQ with EF Core. + +- Learning about Regex Patterns. +Regex patterns is something I've seen before but I never really understand what it does. It's basically a way for the computer to identify the pattern such as e-mails "@", domain suffixes for email and phone number sequences to be extracted from a string. +![Image](Assets/5.png) + + +## References +- https://www.entityframeworktutorial.net/efcore/entity-framework-core.aspx +- https://www.youtube.com/watch?v=SryQxUeChMc&list=PLdo4fOcmZ0oXCPdC3fTFA3Z79-eVH3K-s&index=1 +- https://uibakery.io/regex-library/phone-number-csharp +- https://www.youtube.com/watch?v=V_DzcyGTXW0 +- Google Gemini \ No newline at end of file