I'm making a Listener, so that I'll be able to post gamedata form multiple games to my server. I will use that to keep clients in sync, no matter what device they play on! This part I'm done with, Using Entity and Linq for inserts and loading playerdata when they are desync'ed.
Every webpost to the server takes about 2-10 ms, depending on the data beeing sent.
(which i hope is fine!). This is mostly FirstOrDefault selects, modyfing some data and SaveChanges.
But the server also has to feed the clients with Scoreboards. both "Today/All Time" and "Everyone/Friends only".
I tried with some Linq SQL queries. but when i look at the output I'm really scared of how its gonna preform (with hopfully thousands of users, and millions of score records).
Here's an example:
using (BertEntities BGE = new BertEntities())
{
var query =
(from score in BGE.ScoreTable
join game in BGE.GameSession on score.GameSessionID equals game.ID
join user in BGE.User on game.UserID equals user.ID
where
game.PublicHighScore == true &&
game.Banned == false &&
game.GameID == GameID &&
score.LevelID == level
orderby score.Score descending
select new ScoreResult
{
ID = user.ID,
Name = user.FacebookName,
Score = score.Score,
Streak = score.Streak,
Time = score.Time
}).Skip(skip).Take(count);
return JsonConvert.SerializeObject(query.ToList());
}
Which gets executed like this
SELECT TOP (100)
[Project1].[Score] AS [Score],
[Project1].[ID] AS [ID],
[Project1].[FacebookName] AS [FacebookName],
[Project1].[Streak] AS [Streak],
[Project1].[Time] AS [Time]
FROM ( SELECT [Project1].[Score] AS [Score], [Project1].[Streak] AS [Streak], [Project1].[Time] AS [Time], [Project1].[ID] AS [ID], [Project1].[FacebookName] AS [FacebookName], row_number() OVER (ORDER BY [Project1].[Score] ASC) AS [row_number]
FROM ( SELECT
[Filter1].[Score] AS [Score],
[Filter1].[Streak] AS [Streak],
[Filter1].[Time] AS [Time],
[Extent3].[ID] AS [ID],
[Extent3].[FacebookName] AS [FacebookName]
FROM (SELECT [Extent1].[LevelID] AS [LevelID], [Extent1].[Score] AS [Score], [Extent1].[Streak] AS [Streak], [Extent1].[Time] AS [Time], [Extent2].[UserID] AS [UserID], [Extent2].[GameID] AS [GameID]
FROM [dbo].[ScoreTable] AS [Extent1]
INNER JOIN [dbo].[GameSession] AS [Extent2] ON [Extent1].[GameSessionID] = [Extent2].[ID]
WHERE (1 = [Extent2].[PublicHighScore]) AND (0 = [Extent2].[Banned]) ) AS [Filter1]
INNER JOIN [dbo].[User] AS [Extent3] ON [Filter1].[UserID] = [Extent3].[ID]
WHERE ([Filter1].[GameID] = @p__linq__0) AND ([Filter1].[LevelID] = @p__linq__1)
) AS [Project1]
) AS [Project1]
WHERE [Project1].[row_number] > 0
ORDER BY [Project1].[Score] DESC
I'm no SQL expert, but that looks pretty tough to me. (Taking into account that I want this to scale, even with millions of records and thousands of requests without killing the server.)
This is the database layout:
www.invokergame.com/db.png
I'm thinking of storing all the data I need in the ScoreTable table, so I'd avoid making the joins. but that would result in a lot of duplicate data, and I'm pretty sure that's the wrong approach.
Maybe I should forget about Entity Linq, and write it in ADO.NET SQL (which I'd need help with)?
Sorry about the wall of text, I'm looking forward for some wise words!
Edit:
Okay TomTom, Is this what you ment by faking it? Dragging all the data I need into one dataset. And then use LINQ to update or remove and select items from this list, when a user makes a new highscore, or disable PublicHighScore?
And how will this perform?
public class ScoreCacheItem
{
public Int64 ScoreTableID { get; set; }
public Guid GameID { get; set; }
public int LevelID { get; set; }
public Int64 UserID { get; set; }
public string Name { get; set; }
public int Score { get; set; }
public int Streak { get; set; }
public double Time { get; set; }
public DateTime Today { get; set; }
public int Today_Score { get; set; }
public int Today_Streak { get; set; }
public double Today_Time { get; set; }
}
public sealed class ScoreCacheSystem
{
private List<ScoreCacheItem> FScoreCache = new List<ScoreCacheItem>();
private object FLock = new object();
private List<ScoreCacheItem> LoadAllScores()
{
using (BertEntities BGE = new BertEntities())
{
var query =
(from score in BGE.ScoreTable
join game in BGE.GameSession on score.GameSessionID equals game.ID
join user in BGE.User on game.UserID equals user.ID
where
game.PublicHighScore == true &&
game.Banned == false
orderby score.Score descending
select new ScoreCacheItem
{
ScoreTableID = score.ID,
GameID = game.GameID,
LevelID = score.LevelID,
UserID = user.ID,
Name = user.FacebookName,
Score = score.Score,
Streak = score.Streak,
Time = score.Time,
Today = score.Today,
Today_Score = score.Today_Score,
Today_Streak = score.Today_Streak,
Today_Time = score.Today_Time
});
return query.ToList();
}
}
public List<ScoreCacheItem> HighScores {
get
{
lock (FLock)
{
if (FScoreCache == null)
{
FScoreCache = LoadAllScores();
}
return FScoreCache;
}
}
}
}