Today was a long day of coding to create the unified feed, Twitter-X style
LOL
The feature requires a ton of changes and refactoring, on top of the current "forum-like" architecture.
This is due to the fact that when I am showing the posts within their forum and their threads, all the shown posts share the same forum and thread.
So, for each post, they are the same.
Instead of a unified feed where each post must maintain the pointers to the respective threads, forums, and authors (author of the post and thread OP), I need to maintain that information for each and every post.
So for instance, to extract the posts, I was using something like the following. The extra needed information (use an avatar and nickname of the poster) was simply obtained by joining, and the main information about the thread and forum was simply the current thread and forum.
Never mind the 3 nested selects that are needed for result pagination (in case ROW_NUMBER() method is not available, like in SQLSERVER. Remember that I am making this compatible with most relational DBMSes, with multiple connectivity).
The problem is that in a "unified feed", the concept of the current thread or forum is meaningless, as each post can belong everywhere.
This forces a bit of rethinking of the extraction and join process.
What I have done is to simplify the paginated SELECT of the post, by extracting only the post and then rejoining the corresponding objects programmatically.
A draft of the new procedure would look like this:
Then I would create dictionaries of all the threads and forums and send those to the client through serialization, where the objects are recreated and "rejoined" to the respective posts.
This way I can avoid replicating all the information about threads and forums for each post, and maintain a list of unique objects which are recreated and relinked on the client.
So in the end, it required a full day, and I am not yet finished as there are still many bugs running everywhere to be patiently killed one by one
but I am pretty happy with the journey
A good way to celebrate my birthday after all 
In practice, this new feature has forced the code to become conceptually more abstract but also more maintainable.
It's pretty much like that when you work on a project. The more sides you start "hitting" it, the more powerful and abstract become every object you use, often with a gain in maintainability...
LOLThe feature requires a ton of changes and refactoring, on top of the current "forum-like" architecture.
This is due to the fact that when I am showing the posts within their forum and their threads, all the shown posts share the same forum and thread.
So, for each post, they are the same.
Instead of a unified feed where each post must maintain the pointers to the respective threads, forums, and authors (author of the post and thread OP), I need to maintain that information for each and every post.
So for instance, to extract the posts, I was using something like the following. The extra needed information (use an avatar and nickname of the poster) was simply obtained by joining, and the main information about the thread and forum was simply the current thread and forum.
Code:
public System.Collections.Generic.List<ThreadPost> SelectAllThreadPosts_PAGED(int ThreadID, int PageNumber, bool AllPostsVirtualThread)
{
string WhereThreadClause;
int PageSizeUsed;
string Order_SelectUpToPage;
string Order_SelectBringPageOnTop;
string Order_SelectPageOrdered;
string SortingField;
if (AllPostsVirtualThread)
{
WhereThreadClause = "";
PageSizeUsed = _PAGE_SIZE_ALL_POSTS;
Order_SelectUpToPage = "DESC";
Order_SelectBringPageOnTop = "ASC";
Order_SelectPageOrdered = "DESC";
SortingField = "DateOfLastUpload"; // may be DbNull
}
else
{
WhereThreadClause = "ThreadPosts.ThreadID=@ThreadID AND";
PageSizeUsed = _PAGE_SIZE_POSTS;
Order_SelectUpToPage = "ASC";
Order_SelectBringPageOnTop = "DESC";
Order_SelectPageOrdered = "ASC";
SortingField = "PostID"; // in place of SortingField = "DateOfPostCreation"
}
string SelectPageOrdered;
if (!this._USE_SQL_SERVER)
{
// record fino alla pagina corrent ordinati
int EstremoSuperiore = PageNumber * PageSizeUsed;
if (EstremoSuperiore <= 0)
EstremoSuperiore = 1;
string SelectUpToPage = "Select TOP " + EstremoSuperiore + " ThreadPosts.*, Users.NicknameUser, Users.AvatarFileUser FROM ThreadPosts LEFT JOIN Users On ThreadPosts.UserAuthorID=Users.UserID" + " WHERE " + WhereThreadClause + " ThreadPosts.StatusPost=0 And (Users.StatusUser=0 Or Users.StatusUser Is NULL) ORDER BY " + SortingField + " " + Order_SelectUpToPage;
string SelectBringPageOnTop = "SELECT TOP " + PageSizeUsed + " sub.* FROM (" + SelectUpToPage + ") sub ORDER BY sub." + SortingField + " " + Order_SelectBringPageOnTop;
SelectPageOrdered = "SELECT * FROM (" + SelectBringPageOnTop + ") subOrdered ORDER BY subOrdered." + SortingField + " " + Order_SelectPageOrdered + ";";
}
else
{
int EstremoSuperiore = PageNumber * PageSizeUsed; // same as: StartingPoint + PageSizeUsed - 1
int StartingPoint = EstremoSuperiore - PageSizeUsed + 1;
string MySQL_Internal = "SELECT TOP " + EstremoSuperiore + "ROW_NUMBER() OVER (ORDER BY " + SortingField + " " + Order_SelectPageOrdered + ") AS RowNum," + " ThreadPosts.*, Users.NicknameUser, Users.AvatarFileUser FROM ThreadPosts LEFT JOIN Users ON ThreadPosts.UserAuthorID=Users.UserID" + " WHERE " + WhereThreadClause + " ThreadPosts.StatusPost=0";
SelectPageOrdered = "SELECT * FROM (" + MySQL_Internal + ") AS RowConstrainedResult WHERE RowNum >= " + StartingPoint + " AND RowNum <= " + EstremoSuperiore + " ORDER BY RowNum ;";
}
using (var MyConn = MakeConnection())
{
MyConn.Open();
using (var MyCmd = this.MakeCommand(SelectPageOrdered, MyConn))
{
MyCmd.Parameters.Add("@ThreadID", System.Data.OleDb.OleDbType.Integer).Value = ThreadID;
using (var r = MyCmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
if (r.HasRows)
{
var Posts = new System.Collections.Generic.List<ThreadPost>();
while (r.Read())
{
ThreadPost ThreadPost;
if (this._USE_SQL_SERVER)
ThreadPost = this.Fill_ThreadPost_FromDBFields(r, 1);
else
ThreadPost = this.Fill_ThreadPost_FromDBFields(r, 0);
Posts.Add(ThreadPost); // key string, richiesto dal serializzatore
}
return Posts;
}
}
}
}
return null;
}
Never mind the 3 nested selects that are needed for result pagination (in case ROW_NUMBER() method is not available, like in SQLSERVER. Remember that I am making this compatible with most relational DBMSes, with multiple connectivity).
The problem is that in a "unified feed", the concept of the current thread or forum is meaningless, as each post can belong everywhere.
This forces a bit of rethinking of the extraction and join process.
What I have done is to simplify the paginated SELECT of the post, by extracting only the post and then rejoining the corresponding objects programmatically.
A draft of the new procedure would look like this:
Code:
public void SelectAllThreadPosts_PAGED(int ThreadID, int PageNumber, ref System.Collections.Generic.List<ThreadPost> ListOfSortedPosts, ref Dictionary<string, UserInfo> DictOfDifferentUsers, ref Dictionary<string, ForumThread> DictOfDifferentThreads, ref Dictionary<string, Forum> DictOfDifferentForums)
{
int PageSizeUsed;
string Order_SelectUpToPage;
string Order_SelectBringPageOnTop;
string Order_SelectShownPage;
string SortingField;
if (ThreadID == _KEY_GLOBAL_VIRTUAL_THREAD_ALL_POST)
{
// WhereThreadClause = ""
PageSizeUsed = _PAGE_SIZE_ALL_POSTS;
Order_SelectUpToPage = "DESC";
Order_SelectBringPageOnTop = "ASC";
Order_SelectShownPage = "DESC";
SortingField = "DateOfLastUpload"; // may be DbNull
}
else
{
PageSizeUsed = _PAGE_SIZE_POSTS;
Order_SelectUpToPage = "ASC";
Order_SelectBringPageOnTop = "DESC";
Order_SelectShownPage = "ASC";
SortingField = "PostID"; // in place of "DateOfPostCreation"
}
int EstremoSuperiore = Math.Max(1, PageNumber * PageSizeUsed);
string SelectUpToPage = "Select TOP " + EstremoSuperiore + " ThreadPosts.*" + FROM_forPosts() + WHERE_forPosts(ThreadID == _KEY_GLOBAL_VIRTUAL_THREAD_ALL_POST) + " ORDER BY " + SortingField + " " + Order_SelectUpToPage;
string SelectReverseChunk = "SELECT TOP " + PageSizeUsed + " UpToPage.* FROM (" + SelectUpToPage + ") UpToPage ORDER BY UpToPage." + SortingField + " " + Order_SelectBringPageOnTop;
string SelectPageOrdered = "SELECT * FROM (" + SelectReverseChunk + ") ReversedChunk ORDER BY ReversedChunk." + SortingField + " " + Order_SelectShownPage + ";";
using (var MyConn = MakeConnection())
{
MyConn.Open();
using (var MyCmd = this.MakeCommand(SelectPageOrdered, MyConn))
{
if (ThreadID != _KEY_GLOBAL_VIRTUAL_THREAD_ALL_POST)
MyCmd.Parameters.Add("@ThreadID", System.Data.OleDb.OleDbType.Integer).Value = ThreadID;
using (var r = MyCmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection))
{
if (r.HasRows)
{
while (r.Read())
{
ThreadPost ThreadPost = this.Fill_ThreadPost_FromDBFields(r, 0, forJoin: false);
this.Crea_Stringa_PostFileAndFolderSuServer(ThreadPost.FileNamePost, ThreadPost.FileNamePostWithFolder, ThreadPost.FileNamePostPhysicalPathOnServer);
if (!DictOfDifferentUsers.ContainsKey(ThreadPost.UserAuthorID))
{
UserInfo UserInfo_PostOP = this.getUserInfoFrom_UserID(ThreadPost.UserAuthorID);
if (UserInfo_PostOP != null)
{
{
var withBlock = UserInfo_PostOP;
DictOfDifferentUsers.Add(UserInfo_PostOP.UserID, UserInfo_PostOP);
this.Crea_Stringa_UserAvatarFileAndFolderSuServer(withBlock.AvatarFileUser, withBlock.AvatarFileUserWithFolder, withBlock.AvatarFileUserPathPhysicalOnServer);
}
}
}
// thread
if (!DictOfDifferentThreads.ContainsKey(ThreadPost.ThreadID))
{
ForumThread ForumThread = this.getForumThreadFrom_ThreadID(ThreadPost.ThreadID);
DictOfDifferentThreads.Add(ForumThread.ThreadID, ForumThread);
// autore thread
if (!DictOfDifferentUsers.ContainsKey(ForumThread.OpUserID))
{
UserInfo UserInfo_ThreadOP = this.getUserInfoFrom_UserID(ForumThread.OpUserID);
if (UserInfo_ThreadOP != null)
{
{
var withBlock = UserInfo_ThreadOP;
DictOfDifferentUsers.Add(withBlock.UserID, UserInfo_ThreadOP);
this.Crea_Stringa_UserAvatarFileAndFolderSuServer(withBlock.AvatarFileUser, withBlock.AvatarFileUserWithFolder, withBlock.AvatarFileUserPathPhysicalOnServer);
}
}
}
// forum
if (!DictOfDifferentForums.ContainsKey(ForumThread.ForumID))
{
Forum Forum = this.getForumFrom_ForumID(ForumThread.ForumID);
DictOfDifferentForums.Add(Forum.ForumID, Forum);
}
}
ListOfSortedPosts.Add(ThreadPost);
}
}
}
}
}
}
Then I would create dictionaries of all the threads and forums and send those to the client through serialization, where the objects are recreated and "rejoined" to the respective posts.
This way I can avoid replicating all the information about threads and forums for each post, and maintain a list of unique objects which are recreated and relinked on the client.
So in the end, it required a full day, and I am not yet finished as there are still many bugs running everywhere to be patiently killed one by one
but I am pretty happy with the journey
A good way to celebrate my birthday after all 
In practice, this new feature has forced the code to become conceptually more abstract but also more maintainable.
It's pretty much like that when you work on a project. The more sides you start "hitting" it, the more powerful and abstract become every object you use, often with a gain in maintainability...

Last edited: