Je travaille sur un projet ASP.NET avec C # et Sql Server 2008.
J'ai trois tableaux:
Chaque utilisateur a une valeur spécifique pour chaque champ de données, et cette valeur est stockée dans les DataFieldsValues.
Maintenant, je veux afficher un rapport qui ressemble à ceci:
J'ai créé les objets User
et DataField
. Dans l'objet DataField, il y a la méthode string GetValue(User user)
, dans laquelle j'obtiens la valeur d'un champ pour un certain utilisateur.
Ensuite, j'ai la liste des utilisateurs List<User> users
et la liste des DataFields List<DataField> fields
et je fais ce qui suit:
string html = string.Empty;
html += "<table>";
html += "<tr><th>Username</th>";
foreach (DataField f in fields)
{
html += "<th>" + f.Name + "</th>";
}
html += "</tr>"
foreach (User u in users)
{
html += "<tr><td>" + u.Username + "</td>"
foreach (DataField f in fields)
{
html += "<td>" + f.GetValue(u) + "</td>";
}
html += "</tr>"
}
Response.Write(html);
Cela fonctionne bien, mais c'est extrêmement lent, et je parle de 20 utilisateurs et de 10 champs de données. Existe-t-il un meilleur moyen en termes de performances pour y parvenir?
EDIT: Pour chaque paramètre à l'intérieur des classes, je récupère la valeur en utilisant la méthode suivante:
public static string GetDataFromDB(string query)
{
string return_value = string.Empty;
SqlConnection sql_conn;
sql_conn = new SqlConnection(ConfigurationManager.ConnectionStrings["XXXX"].ToString());
sql_conn.Open();
SqlCommand com = new SqlCommand(query, sql_conn);
//if (com.ExecuteScalar() != null)
try
{
return_value = com.ExecuteScalar().ToString();
}
catch (Exception x)
{
}
sql_conn.Close();
return return_value;
}
Par exemple:
public User(int _Id)
{
this.Id = _Id
this.Username = DBAccess.GetDataFromDB("select Username from Users where Id=" + this.Id)
//...
}
Voici 2 suggestions qui vous aideront. La première suggestion est ce qui améliorera considérablement vos performances. La deuxième suggestion vous aidera également, mais ne rendra probablement pas votre application plus rapide dans votre cas.
Vous appelez très souvent la méthode GetDataFromDB(string query)
. C'est mauvais car vous créez à chaque fois un nouveau SqlConnection et SqlCommand. Cela prend du temps et des ressources. De plus, en cas de retard sur le réseau, celui-ci est multiplié par le nombre d'appels que vous effectuez. C'est juste une mauvaise idée.
Je vous suggère d'appeler cette méthode une fois et de la faire remplir une collection comme un Dictionary<int, string>
afin que vous puissiez rechercher rapidement votre valeur de nom d'utilisateur à partir de la clé d'ID utilisateur.
Comme ça:
// In the DataField class, have this code.
// This method will query the database for all usernames and user ids and
// return a Dictionary<int, string> where the key is the Id and the value is the
// username. Make this a global variable within the DataField class.
Dictionary<int, string> usernameDict = GetDataFromDB("select id, username from Users");
// Then in the GetValue(int userId) method, do this:
public string GetValue(int userId)
{
// Add some error handling and whatnot.
// And a better name for this method is GetUsername(int userId)
return this.usernameDict[userId];
}
Voici une autre façon d'améliorer les choses, quoique légèrement dans ce cas: utilisez la classe StringBuilder
. Il y a des gains de performances importants (voici un aperçu: http://support.Microsoft.com/kb/306822 ).
SringBuilder sb = new StringBuilder();
sb.Append("<table><tr><th>Username</th>");
foreach (DataField f in fields)
{
sb.Append("<th>" + f.Name + "</th>");
}
// Then, when you need the string
string html = sb.ToString();
Faites-moi savoir si vous avez besoin de plus de précisions, mais ce que vous demandez est très faisable. Nous pouvons résoudre ce problème!
Si vous effectuez ces 2 changements simples, vous aurez de grandes performances. Je le garantis.
La conception de base de données que vous choisissez s'appelle Entity-Attribute-Value , une conception bien connue pour ses problèmes de performances. L'équipe SQL Server a publié un livre blanc pour des conseils sur la conception d'EAV, voir Meilleures pratiques pour la modélisation de données sémantiques pour les performances et l'évolutivité .
Hélas, vous avez déjà le design en place et que pouvez-vous faire maintenant? L'important est de réduire les appels miriad au dB en un seul appel et d'exécuter une seule instruction orientée ensemble pour récupérer les données. Le nom du jeu est Table Valued Parameters :
declare @users as UsersType;
insert into @users (UserId) values (7), (42), (89);
select ut.Id,
ut.Username,
df.Name as DataFieldName,
dfv.Value
from Users ut
join @users up on ut.Id = up.UserId
join DataFieldValues dfv on ut.Id = dfv.UserId
join DataFields df on dfv.DataFieldId = df.Id
order by ut.Id;
Pour un exemple complet, voir ceci SqlFiddle .
Alors qu'à proprement parler, il est possible de récupérer un résultat sur la forme souhaitée (noms de champs de données transposés en noms de colonnes) en utilisant l'opérateur PIVOT
, je déconseille fortement de faire donc. PIVOT est à lui seul un bourbier de performances, mais lorsque vous ajoutez la nature dynamique de l'ensemble de résultats souhaité, il est pratiquement impossible de le retirer. L'ensemble de résultats traditionnel composé d'un attribut d'une ligne par est trivial à analyser dans une table, car l'ordre requis par l'ID utilisateur garantit une rupture nette entre les ensembles d'attributs corrélés.
C'est lent car sous le capot, vous effectuez 20 x 10 = 200 requêtes vers la base de données. La bonne façon serait de tout charger en un seul tour.
Vous devez publier quelques détails sur la façon dont vous chargez les données. Si vous utilisez Entity Framework, vous devez utiliser quelque chose appelé Eager Loading en utilisant la commande Include.
// Load all blogs and related posts
var blogs1 = context.Blogs
.Include(b => b.Posts)
.ToList();
Quelques exemples peuvent être trouvés ici: http://msdn.Microsoft.com/en-us/data/jj574232.aspx
ÉDITER:
Il semble que vous n'utilisez pas les outils que .NET Framework vous offre. De nos jours, vous n'avez pas à faire votre propre accès à la base de données pour des scénarios simples comme le vôtre. En outre, vous devez éviter de concaténer le code HTML de chaîne comme vous le faites.
Je vous suggère de repenser votre application en utilisant les contrôles ASP.NET et Entity Framework existants.
Voici un exemple avec des instructions étape par étape pour vous: http://www.codeproject.com/Articles/363040/An-Introduction-to-Entity-Framework-for-Absolute-B
Comme l'a dit Remus Rusanu, vous pouvez obtenir les données que vous voulez dans le format dont vous avez besoin en utilisant l'opérateur relationnel PIVOT, en ce qui concerne les performances de PIVOT, j'ai constaté que cela dépendra de l'indexation de vos tables et de la variabilité et la taille de l'ensemble de données. Je serais très intéressé à en savoir plus sur son opinion sur les PIVOTs, car nous sommes tous ici pour apprendre. Il y a une grande discussion sur PIVOT vs JOINS ici .
Si la table DataFields est un ensemble statique, vous n'aurez peut-être pas à vous soucier de générer dynamiquement le SQL et vous pourrez vous construire une procédure stockée; si cela varie, vous devrez peut-être prendre le coup de performance de SQL dynamique (voici un excellent article à ce sujet) ou utiliser une approche différente.
À moins que vous n'ayez davantage besoin des données, essayez de conserver l'ensemble renvoyé au minimum dont vous avez besoin pour l'affichage, c'est un bon moyen de réduire les frais généraux, car tout devra passer par le réseau, sauf si votre base de données se trouve sur le même serveur physique que le serveur Web. .
Assurez-vous que vous effectuez le moins d'appels de données distincts possible afin de réduire le temps passé à augmenter et à couper les connexions.
Vous devez toujours vérifier les appels de données dans une boucle lorsque le contrôle de la boucle est basé sur un ensemble de données (probablement lié?) Car cela crie JOIN.
Lorsque vous expérimentez votre SQL, essayez de vous familiariser avec les plans d'exécution, cela vous aidera à comprendre pourquoi vous avez des requêtes à exécution lente, consultez ces ressources pour plus d'informations.
Quelle que soit votre approche, vous décidez que vous devez déterminer où se trouvent les goulots d'étranglement dans votre code, quelque chose d'aussi basique que de passer à travers l'exécution peut vous aider car cela vous permettra de voir par vous-même où se trouvent les problèmes, cela vous permettra également d'identifier pour vous-même des problèmes possibles avec votre approche et construisez de bonnes habitudes de choix de conception.
Marc Gravel a quelques remarques intéressantes à faire sur la lecture des données c # ici l'article est un peu ancien mais mérite d'être lu.
PIVOTAGE de vos données. (Désolé Remus ;-)) Sur la base de l'exemple de données que vous avez fourni, le code suivant obtiendra ce dont vous avez besoin sans récursivité des requêtes:
--Test Data
DECLARE @Users AS TABLE ( Id int
, Username VARCHAR(50)
, Name VARCHAR(50)
, Email VARCHAR(50)
, [Role] INT --Avoid reserved words for column names.
, Active INT --If this is only ever going to be 0 or 1 it should be a bit.
);
DECLARE @DataFields AS TABLE ( Id int
, Name VARCHAR(50)
, [Type] INT --Avoid reserved words for column names.
);
DECLARE @DataFieldsValues AS TABLE ( Id int
, UserId int
, DataFieldId int
, Value VARCHAR(50)
);
INSERT INTO @users ( Id
, Username
, Name
, Email
, [Role]
, Active)
VALUES (1,'enb081','enb081','[email protected]',2,1),
(2,'Mack','Mack','[email protected]',1,1),
(3,'Bob','Bobby','[email protected]',1,0)
INSERT INTO @DataFields
( Id
, Name
, [Type])
VALUES (1,'DataField1',3),
(2,'DataField2',1),
(3,'DataField3',2),
(4,'DataField4',0)
INSERT INTO @DataFieldsValues
( Id
, UserId
, DataFieldId
, Value)
VALUES (1,1,1,'value11'),
(2,1,2,'value12'),
(3,1,3,'value13'),
(4,1,4,'value14'),
(5,2,1,'value21'),
(6,2,2,'value22'),
(7,2,3,'value23'),
(8,2,4,'value24')
--Query
SELECT *
FROM
( SELECT ut.Username,
df.Name as DataFieldName,
dfv.Value
FROM @Users ut
INNER JOIN @DataFieldsValues dfv
ON ut.Id = dfv.UserId
INNER JOIN @DataFields df
ON dfv.DataFieldId = df.Id) src
PIVOT
( MIN(Value) FOR DataFieldName IN (DataField1, DataField2, DataField3, DataField4)) pvt
--Results
Username DataField1 DataField2 DataField3 DataField4
enb081 value11 value12 value13 value14
Mack value21 value22 value23 value24
La chose la plus importante à retenir est d'essayer par vous-même car tout ce que nous suggérons pourrait être modifié par des facteurs sur votre site que nous ne connaissons pas.
Assurez-vous que vous n'établissez pas de connexion à la base de données pour chaque boucle.
Comme je peux le voir, la partie f.GetValue (u) est une méthode qui renvoie une valeur de chaîne extraite de la base de données.
Mettez les données dans un objet une fois pour toutes et faites la même chose que f.GetValue (u) fait ici.
Comment accédez-vous à la base de données? Vérifiez le SQL généré à partir de ces requêtes avec le profileur, si vous utilisez EF, par exemple. N'établissez pas de connexion à chaque fois dans la boucle foreach.
Je ne construirais pas également le HTML du côté serveur. Renvoyez simplement l'objet pour un contrôle de source de données de page.
Le pire problème là-bas: des tonnes d'aller-retour à la base de données. Chaque fois que vous obtenez une valeur, une demande passe par le réseau et attend le résultat.
Si vous devez d'abord avoir la liste des utilisateurs dans le code, assurez-vous que:
Cela devrait faire une énorme différence. Avec ces 2 requêtes spécifiques, vous avez supprimé le bruit du réseau et pouvez vous concentrer sur l'amélioration des index si nécessaire.
Un autre gain que vous obtiendrez est sur l'ensemble des cordes de concaténation. La première étape consiste à remplacer par un StringBuilder. L'étape suivante consiste à écrire directement dans le flux de sortie, vous n'avez donc pas besoin de conserver toutes ces données en mémoire ... mais il est peu probable que vous en ayez besoin; et si vous le faites en raison de trop de données, vous aurez de toute façon des problèmes avec les navigateurs.
ps. pas le scénario OP, mais pour ceux qui ont besoin de vitesse en masse, vous voulez plutôt exporter en masse: http://technet.Microsoft.com/en-us/library/ms175937.aspx
Utilisez Indexé pour le champ de clé primaire de la table et dans le code derrière utiliser le générateur de chaînes.
RAPIDE ... UTILISATION
utiliser un lecteur
SqlDataReader dbReader = mySqlCommand.ExecuteReader();
//if reader has row values
if (dbReader.HasRows) // while(xxx) for more rows return
{
//READ DATA
}
FAITES LES INDICES APPROPRIÉS si besoin est pour les partitions ...
Utilisation et astuces pour SELECT NOLOCK fonctionnent pour moi
Conseils de requête (Transact-SQL) http://technet.Microsoft.com/en-us/library/ms181714.aspx
Conseils de verrouillage http://technet.Microsoft.com/en-us/library/aa213026 (v = sql.80) .aspx
Oui, la seule fois où j'utiliserai LINQ sera si j'appelle une procédure stockée.
Rechercher LINQ to SQL
MAIS JE SUIS UNE VIEILLE ÉCOLE ....
Ce Entity Framework je m'en débarrasse car Entity Framework 1.0 est bon lorsque vous faites un projet d'école ...
Mais c'est très cher comme instance de calcul ...
LIRE TOUT DANS LA MÉMOIRE FAIRE QUELQUE CHOSE ???? POURQUOI JE PAYE POUR SQL? UTILISEZ une structure de fichier JSON puis ....