JSON + C# + SQL = nya möjligheter

By | oktober 28, 2016

En av de minst uppmärksammade nyheterna i SQL Server 2016 och Azure SQL Databases är stödet för JSON. JSON är ett kompakt, textbaserat format för att utbyta objekt. Just detta gör att JSON kan fungera mycket bra som brygga mellan den ”objektorienterade världen” (C# och liknande språk) samt ”relationsvärlden” (SQL databaser). Detta är egentligen ingen större skillnad mot exempelvis XML, men JSON har sina fördelar i att det är effektivare.

Exempelvis en klass i C# kan se ut så här:


public class Team
{
public int TeamId;
public string TeamName;
public Player[] Players;
public MatchDetails[] TeamMatchDetails;
}

När vi representerar den i en databas så kommer vi att behöva minst tre tabeller:

  • Team
  • Player
  • MatchDetails

Att då hämta all information om ett Team innebär då att vi står inför två huvudalternativ:

Vi gör en SELECT för varje tabell.

SELECT ...
FROM Team
WHERE Team.TeamName = ...

SELECT ...
FROM Player
WHERE Player.TeamId = ...

SELECT ...
FROM MatchDetails
WHERE MatchDetails.HomeTeamId = ... OR MatchDetails.OtherTeamId = ...

Vi hämtar allt i ett och samma SQL-anrop genom att göra en JOIN mellan tabellerna.


SELECT ...
FROM Team
INNER JOIN Player ON Team.TeamId = Player.TeamId
INNER JOIN MatchDetails ON Team.TeamId = MatchDetails.HomeTeamID OR Team.TeamId = MatchDetails.OtherTeamId
WHERE ...

Problemet med att göra tre SELECT-satser är att det är en hel del overhead för varje SELECT-sats. Även om moderna versioner av SQL Server stödjer MARS (Multiple Active Result Sets), så tar det en hel del extra tid att hämta resultatet från tre SELECT-satser än en enda.

Om vi å andra sidan använder en JOIN mellan tabellerna för att leverera all data i en enda SELECT-sats så får vi mycket onödig repetition av data.

Tredje alternativet: JSON

Med hjälp av JSON kan vi klara att skicka all data genom endast en SELECT-sats och vi undviker onödig repetition av data.

SELECT
	Team.TeamId
	,Team.TeamName
	,Players = (
	  SELECT
		Player.PlayerId
		,Player.PlayerName
	  FROM Player
	  WHERE Player.TeamId = Team.TeamId
	  FOR JSON AUTO
	)
	,TeamMatchDetails = (
	  SELECT
		MatchDetails.HomeTeamId
		,MatchDetails.OtherTeamId
		,MatchDetails.MatchDate
		,MatchDetails.HomeTeamGoals
		,MatchDetails.OtherTeamGoals
	  FROM MatchDetails
	  WHERE MatchDetails.HomeTeamId = Team.TeamId OR MatchDetails.OtherTeamId = Team.TeamId
	  FOR JSON AUTO
	)
FROM Team
WHERE TeamName = 'Eagles'
FOR JSON AUTO, WITHOUT_ARRAY_WRAPPER ;

Om du tidigare har använt XML-funktionaliteten i SQL Server så känner du säkert igen mycket av lösningen. FOR JSON AUTO är den klausul som omvandlar tabelldata till JSON-format. Eftersom vi vet att vi bara får en rad så har jag också lagt till WITHOUT_ARRAY_WRAPPER.

Resultatet av denna SQL-sats skulle då kunna se ut så här:

{
"TeamId":1,
"TeamName":"Eagles",
"Players":[{"PlayerId":1,"PlayerName":"Adrian"},{"PlayerId":2,"PlayerName":"Ben"}],
"TeamMatchDetails":[{"HomeTeamId":1,"OtherTeamId":2,"MatchDate":"2016-10-01","HomeTeamGoals":5,"OtherTeamGoals":3}]
}

Inläsning av resultatet i vår C#-klass

Nu kommer vi till det allra bästa. Hur läser vi in ovanstående resultat i vår C#-klass?

Med hjälp av Json.NET (ett Open Source ramverk) går det superenkelt:

var team = (Team)JsonConvert.DeserializeObject(jsontext, typeof(Team));

Enklare än så här kan det knappast bli. Koden ovan förutsätter naturligtvis att du har exekverat SELECT-satsen och sparat resultatet i variabeln jsontext.

Slutsatser

Som vanligt är detta naturligtvis inte den slutliga lösningen på all läsning av objekt från databaser till C#. Att omvandla till och från JSON tar CPU-kraft det blir en viss overhead jämfört med binära format. Mina prestandatester (där jag haft ännu lite mer avancerade exempel och kommit upp i över 20 000 transaktioner per sekund) visar:

  • Om du har tillräckligt med CPU-kraft (tillräckligt antal kärnor) så är JSON den snabbaste lösningen.
  • Om du inte har tillräckligt med CPU-kraft så blir JSON långsammare än de traditionella lösningarna.

En invändning kan också vara att det ju redan finns ramverk (exempelvis Entity Framework och NHibernate) just för detta ändamål. Min erfarenhet av dessa är dock inte enbart positiv, så för att inte råka ut för överraskningar så tycker jag att JSON är ett bättre alternativ.

Jag vill ändå ge mina reservationer att i många fall är det vanliga ”tabellobjekt” man läser, och då är det naturligtvis dåligt prestandamässigt att gå via JSON. Detta är som sagt inte någon slutlig lösning.

En ytterligare möjlighet är att vända på riktningen och skicka C#-klasser till SQL Server med hjälp av JSON. Även där är JSON mycket användbart, dock med samma reservationer som för läsning.