C
C#3mo ago
paun

In Memory DATABase troubles

Some slight issues and code fix ups
public class InMemoryDatabase
{
public Dictionary<int, Person> People = [];
public Dictionary<int, List<int>>? Friends { get; set; } = [];


public void AddPerson(int id, string? name, int? age) =>
People.Add(id, new Person(id, name, age));

public string? FetchPerson(int personId) =>
People.TryGetValue(personId, out var foundPerson) ? foundPerson.ToString() : null;

public void UpdatePerson(int personId, Person updatedPerson)
{
if (People.TryGetValue(personId, out var foundPerson))
People[foundPerson.Id] = updatedPerson;
}
public void RemovePerson(int personId)
{
if (People.TryGetValue(personId, out var foundPerson))
People.Remove(foundPerson.Id);
}
public IEnumerable<string>? GetFriends(int personId)
{
if(Friends == null)
throw new Exception("No Friends list available");
if (Friends.TryGetValue(personId, out var friendIds))
return People.Where(x => friendIds.Contains(x.Key)).Select(x => x.ToString());
else
return null;
}
}
public class InMemoryDatabase
{
public Dictionary<int, Person> People = [];
public Dictionary<int, List<int>>? Friends { get; set; } = [];


public void AddPerson(int id, string? name, int? age) =>
People.Add(id, new Person(id, name, age));

public string? FetchPerson(int personId) =>
People.TryGetValue(personId, out var foundPerson) ? foundPerson.ToString() : null;

public void UpdatePerson(int personId, Person updatedPerson)
{
if (People.TryGetValue(personId, out var foundPerson))
People[foundPerson.Id] = updatedPerson;
}
public void RemovePerson(int personId)
{
if (People.TryGetValue(personId, out var foundPerson))
People.Remove(foundPerson.Id);
}
public IEnumerable<string>? GetFriends(int personId)
{
if(Friends == null)
throw new Exception("No Friends list available");
if (Friends.TryGetValue(personId, out var friendIds))
return People.Where(x => friendIds.Contains(x.Key)).Select(x => x.ToString());
else
return null;
}
}
No description
52 Replies
paun
paunOP3mo ago
Here's what I'm supposed to do but I'm just currently making a DB first
maxmahem
maxmahem3mo ago
I don't think Friends should be nullable (or settable) here. I would do a get-only autoproperty for both, initialized with [] FetchPerson looks fine. AddPerson as well... although I'd question if you want name and age to be nullable.
paun
paunOP3mo ago
My Logic was to use ID as a primary key and those 2 being nullable wouldn't matter
maxmahem
maxmahem3mo ago
UpdatePerson is fine, I might force a ternary, but I'm silly like that.
paun
paunOP3mo ago
I'm kind of lost about updateperson Cause it says to use a both PUT and PATCH endpoint
maxmahem
maxmahem3mo ago
well, all the more reason to have them be not nullable.
paun
paunOP3mo ago
I understand one is complete and the other one is partial but not sure how to fix it without adding an additional URL I'll change the Person class too then Is it fine to add a json serializer and deseralizer to store the DB in it
maxmahem
maxmahem3mo ago
mmm, web side of C# is not my strong suit, but from the perspective of this in memory DB you can have perhaps a special "update person" type, with nullable fields, and only update the person fields that are not null.
paun
paunOP3mo ago
What about adding discards
maxmahem
maxmahem3mo ago
I'm not sure what you mean by that.
paun
paunOP3mo ago
uhh
maxmahem
maxmahem3mo ago
_ = IReturnSomeThingThatYouDontCareAbout()? like Remove maybe?
paun
paunOP3mo ago
if(name == "Empty field")
Person p = new(Id, _, Age);
if(name == "Empty field")
Person p = new(Id, _, Age);
But then name can't be null :sadge:
maxmahem
maxmahem3mo ago
ah, no, that won't work. Well can a persons name logically be null? Seems like it shouldn't be. If this is stemming from the demands of Update just have a special UpdatePerson type for that case. On the whole it looks good. Removing that nullability I think will help you tighten up your logic a bit, but its pretty fine as is. Anyways Update could be like...
public oid UpdatePerson(int persoonId, UpdatePerson updatePerson) {
if (People.TryGetValue(personid, out var foundPersonId)) {
Person oldPerson = Person[foundPersonId];
Person[foundPersonId] = oldPerson with {
Name = updatePerson.Name ?? oldPerson.Name,
Age = updatePerson.Age ?? oldPersoon.Age
};
}
}
public oid UpdatePerson(int persoonId, UpdatePerson updatePerson) {
if (People.TryGetValue(personid, out var foundPersonId)) {
Person oldPerson = Person[foundPersonId];
Person[foundPersonId] = oldPerson with {
Name = updatePerson.Name ?? oldPerson.Name,
Age = updatePerson.Age ?? oldPersoon.Age
};
}
}
Assuming Person is a record
paun
paunOP3mo ago
It's not that a record but that's better Makes way more sense I'll make an UpdatePersonPatch and Put methods Should I just change Person to record @,ax
maxmahem
maxmahem3mo ago
If you can, yeah.
paun
paunOP3mo ago
public record Person
{
public Person(int id, string name, string age)
{
Id = id;
Name = name;
Age = age;
}

public required int Id { get; set; }
public required string Name { get; set; }
public required string Age { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}, Age: {Age}";
}
public record Person
{
public Person(int id, string name, string age)
{
Id = id;
Name = name;
Age = age;
}

public required int Id { get; set; }
public required string Name { get; set; }
public required string Age { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}, Age: {Age}";
}
I don't think this is fine Id should be init
public required int Id { get; init; }
public required int Id { get; init; }
public record Person
{
public Person() { }

public required int Id { get; init; }
public required string Name { get; set; }
public required string Age { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}, Age: {Age}";
}
public class InMemoryDatabase
{
public Dictionary<int, Person> People = [];
public Dictionary<int, List<int>> Friends { get; set; } = [];


public void AddPerson(int id, string name, string age) =>
People.Add(id, new Person { Id = id, Name = name, Age = age });

public string? FetchPerson(int personId) =>
People.TryGetValue(personId, out var foundPerson) ? foundPerson.ToString() : null;

public void UpdatePersonPut(int personId, Person updatePerson)
{
if (People.TryGetValue(personId, out var foundPerson))
{
Person oldPerson = People[foundPerson.Id];
People[foundPerson.Id] = oldPerson with
{
Name = updatePerson.Name ?? oldPerson.Name,
Age = updatePerson.Age ?? oldPerson.Age
};
}
}
public void RemovePerson(int personId)
{
if (People.TryGetValue(personId, out var foundPerson))
People.Remove(foundPerson.Id);
}
public IEnumerable<string>? GetFriends(int personId)
{
if(Friends == null)
throw new Exception("No Friends list available");
if (Friends.TryGetValue(personId, out var friendIds))
return People.Where(x => friendIds.Contains(x.Key)).Select(x => x.ToString());
else
return null;
}
}
public record Person
{
public Person() { }

public required int Id { get; init; }
public required string Name { get; set; }
public required string Age { get; set; }

public override string ToString() => $"Id: {Id}, Name: {Name}, Age: {Age}";
}
public class InMemoryDatabase
{
public Dictionary<int, Person> People = [];
public Dictionary<int, List<int>> Friends { get; set; } = [];


public void AddPerson(int id, string name, string age) =>
People.Add(id, new Person { Id = id, Name = name, Age = age });

public string? FetchPerson(int personId) =>
People.TryGetValue(personId, out var foundPerson) ? foundPerson.ToString() : null;

public void UpdatePersonPut(int personId, Person updatePerson)
{
if (People.TryGetValue(personId, out var foundPerson))
{
Person oldPerson = People[foundPerson.Id];
People[foundPerson.Id] = oldPerson with
{
Name = updatePerson.Name ?? oldPerson.Name,
Age = updatePerson.Age ?? oldPerson.Age
};
}
}
public void RemovePerson(int personId)
{
if (People.TryGetValue(personId, out var foundPerson))
People.Remove(foundPerson.Id);
}
public IEnumerable<string>? GetFriends(int personId)
{
if(Friends == null)
throw new Exception("No Friends list available");
if (Friends.TryGetValue(personId, out var friendIds))
return People.Where(x => friendIds.Contains(x.Key)).Select(x => x.ToString());
else
return null;
}
}
I stole your method I apologise I don't even know how to change it since it's the best option
maxmahem
maxmahem3mo ago
no need to apoolgize, that's why I wrote it.
paun
paunOP3mo ago
I changed Person to record set Id as Init since it should be immutable Although if I take into consideration what happens once we remove it maybe the IDs should change in value
maxmahem
maxmahem3mo ago
Person can probaby be a one-liner:
public record Person(int Id, string Name, string Age);
public record Person(int Id, string Name, string Age);
no need for a ToString or set. Record already has a nice ToString and when you use the with syntax, there is no need for set (technically you are creating a new object). and since there is then no method that changes a Person object, it can be completely immutable.
paun
paunOP3mo ago
I forgot it did this I thought it was the opposite Although, if I use a constructor which isn't empty it gives me an error for the Add method What about update person Oh With Nevermind
maxmahem
maxmahem3mo ago
yeah you can't use the object initializer syntax like that anymore. But you can just do a normal new
paun
paunOP3mo ago
So with is basically reconstructing Is there a reason why It's kind of silly in my opinion Also, when should I use structs I never used one Classes -> pretty much anything Records -> immutable objects Structs -> ? Well, preferably immutable* And they also don't compare by reference but by value
maxmahem
maxmahem3mo ago
structs are useful for small objects, like say:
struct Point {
int X;
int Y;
}
struct Point {
int X;
int Y;
}
though that could be a record struct as well now. but when in doubt, prefer a class. oh also, no need to check if the id is present in Remove it doesn't throw if the key is not present so you can just do...
public void RemovePerson(int personId)
=> People.Remove(personId);
public void RemovePerson(int personId)
=> People.Remove(personId);
paun
paunOP3mo ago
Just like Add Also, was the LINQ fine GetFriends
maxmahem
maxmahem3mo ago
looking at that now
maxmahem
maxmahem3mo ago
I may have gone a little overboard, feel free to ask if you have any questions: https://sharplab.io/#gist:ddee989aca0b9673a960b823b5dbab13
SharpLab
C#/VB/F# compiler playground.
maxmahem
maxmahem3mo ago
your GetFriend method was probably fine, but you can do better than itterating the entire people table for matches. Am I correct in understanding that friends was a list of people IDs, indexed by a another people ID? and I used a hashset for that since the friend IDs should be unique. I added a AddFriend method and also update the AddPeople method to add a blank friends list for that person when yoou add that person hmm... probably you should remove that entry from the friends list when you remove the person as well. hmm... and also remove the entry in every instance of the friends list. Cascading updates are a pita! might be better than to more closely mirror how it would be in a DB and have the Friends list be a hashset of (PersonId, PersonId)
paun
paunOP3mo ago
Yes Did you use a HashSet cause it's better for iteraing thru it? also, wouldn't the AddFriend thing need to be added back to the HashSet
if (Friends.TryGetValue(personId, out HashSet<int>? friendsIds)
&& People.ContainsKey(friendId))
{
friendsIds.Add(friendId);
}
if (Friends.TryGetValue(personId, out HashSet<int>? friendsIds)
&& People.ContainsKey(friendId))
{
friendsIds.Add(friendId);
}
maxmahem
maxmahem3mo ago
I'm out of control, I have to stop here: https://sharplab.io/#gist:34a5e789cfb6ab7f194111be4f42743d
SharpLab
C#/VB/F# compiler playground.
maxmahem
maxmahem3mo ago
I used a HashSet because it best maps to the problem domain. A unique set of values is a HashSet.
paun
paunOP3mo ago
Could you explain this a bit if it's not an issue
private IEnumerable<string> YieldFriends(HashSet<int> friendIds) {
foreach(int friendId in friendIds) {
if (People.TryGetValue(friendId, out Person? friend)) {
yield return friend.ToString();
}
}
private IEnumerable<string> YieldFriends(HashSet<int> friendIds) {
foreach(int friendId in friendIds) {
if (People.TryGetValue(friendId, out Person? friend)) {
yield return friend.ToString();
}
}
ohhh it jsut returns the people
maxmahem
maxmahem3mo ago
happy to explain anything.
paun
paunOP3mo ago
okay yeah you don't need to It's just an iteration of the HashSet which gives us ToString of each friend what does yield do though
maxmahem
maxmahem3mo ago
But yeah, that's a fancy way to get an IEnumerable out of a function. well... yield is kind of magic but basically it turns the method into a method that returns an IEnumerable you could just build up a list and return that the normal way if you like instead like each element that is yield returned because an element in the enumeration.
paun
paunOP3mo ago
I feel bad now cause I legit took your code isn't this illegal
maxmahem
maxmahem3mo ago
I mean I made it for you. But what is important is that you understand it.
paun
paunOP3mo ago
I mean I do but it'd be kind of hard for me to write it myself in the near future
maxmahem
maxmahem3mo ago
I find looking at solutions to problems to be inspirational. So I tend to give out a lot of implementations. Sorry if I went to far here.
paun
paunOP3mo ago
No no it's fine I understood the majority and I also understood what it improved
maxmahem
maxmahem3mo ago
I don't know how familiar you are with DBs, but the thing with using a hashset of Friendship objects makes more sense if you are used to lookup tables of that sort.
paun
paunOP3mo ago
I've only done SQL But like SQL in Apex
maxmahem
maxmahem3mo ago
likewise, removing the id from the Person class, and using an auto-incrementing value instead.
paun
paunOP3mo ago
I'm gonna do this part myself, including the POST, GET, PUT endpoints
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Urls.Add("https://localhost:5000");

InMemoryDatabase db = new InMemoryDatabase();

app.MapGet("/", () => "Hello World!");

app.MapGet("/person/fetch/{id}", (int id) => db.FetchPerson(id));
app.MapGet("/person/add/{id}/{name}/{age}", (int id, string name, string age) => db.AddPerson(id, name, age));
app.MapGet("/person/update/{id}/{name}/{age}", (int id, string name, string age) => db.UpdatePersonPut(id, new UpdatePersonData(name, age)));
app.MapGet("/person/remove/{id}", (int id) => db.RemovePerson(id));
app.MapGet("/person/friends/{id}", (int id) => db.GetFriends(id));

app.Run();
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.Urls.Add("https://localhost:5000");

InMemoryDatabase db = new InMemoryDatabase();

app.MapGet("/", () => "Hello World!");

app.MapGet("/person/fetch/{id}", (int id) => db.FetchPerson(id));
app.MapGet("/person/add/{id}/{name}/{age}", (int id, string name, string age) => db.AddPerson(id, name, age));
app.MapGet("/person/update/{id}/{name}/{age}", (int id, string name, string age) => db.UpdatePersonPut(id, new UpdatePersonData(name, age)));
app.MapGet("/person/remove/{id}", (int id) => db.RemovePerson(id));
app.MapGet("/person/friends/{id}", (int id) => db.GetFriends(id));

app.Run();
I wanted to keep it simple for now Just try stuff out, see how it works
maxmahem
maxmahem3mo ago
good because I don't know as much about that side of things. anyways in SQL terms the DB would be something like:
CREATE TABLE People (
PersonId INT PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(100) NOT NULL,
Age INT
);

CREATE TABLE Friendships (
PersonId INT NOT NULL,
FriendId INT NOT NULL,
PRIMARY KEY (PersonId, FriendId),
FOREIGN KEY (PersonId) REFERENCES People(PersonId) ON DELETE CASCADE,
FOREIGN KEY (FriendId) REFERENCES People(PersonId) ON DELETE CASCADE,
CONSTRAINT CHK_NotSelfFriendship CHECK (PersonId != FriendId)
);
CREATE TABLE People (
PersonId INT PRIMARY KEY AUTO_INCREMENT,
Name VARCHAR(100) NOT NULL,
Age INT
);

CREATE TABLE Friendships (
PersonId INT NOT NULL,
FriendId INT NOT NULL,
PRIMARY KEY (PersonId, FriendId),
FOREIGN KEY (PersonId) REFERENCES People(PersonId) ON DELETE CASCADE,
FOREIGN KEY (FriendId) REFERENCES People(PersonId) ON DELETE CASCADE,
CONSTRAINT CHK_NotSelfFriendship CHECK (PersonId != FriendId)
);
paun
paunOP3mo ago
It works
paun
paunOP3mo ago
No description
paun
paunOP3mo ago
I basically did this without even looking at anything I'm proud of myself I need to make one for friends which is slightly harder Well it isn't hard, it's just bothersome to write friends
paun
paunOP3mo ago
@maxmahem should I move to the next lecture
No description
paun
paunOP3mo ago
those marked with 0 are easy to do you jsut need to make endpoints for them thanks a lot for the help too
maxmahem
maxmahem3mo ago
hah, well if you look at the code it already did some of thosoe.
paun
paunOP3mo ago
It basically did everything And the stuff it didn't do could be done by calling the methods you've made
Want results from more Discord servers?
Add your server