2009-06-17 5 views
7

En essayant de résoudre:Tables temporaires dans Linq - Quelqu'un a vu un problème avec ça?

Linq .Contains with large set causes TDS error

Je pense que je suis tombé sur une solution, et je voudrais voir si elle est une façon cachère d'aborder le problème.

(résumé court) Je souhaiterais linq-joiner contre une liste d'identifiants d'enregistrement qui ne sont pas (entièrement ou au moins facilement) générés en SQL. C'est une grande liste qui dépasse souvent la limite de 2100 éléments pour l'appel TDS RPC. Donc, ce que j'aurais fait dans SQL est de les jeter dans une table temporaire, et ensuite rejoint contre cela quand j'en avais besoin. J'ai donc fait la même chose dans Linq. J'ai donc fait la même chose avec Linq.

Dans mon fichier MyDB.dbml j'ajouté:

<Table Name="#temptab" Member="TempTabs"> 
    <Type Name="TempTab"> 
    <Column Name="recno" Type="System.Int32" DbType="Int NOT NULL" 
      IsPrimaryKey="true" CanBeNull="false" /> 
    </Type> 
</Table> 

Ouverture du concepteur et fermer ajouté les entrées nécessaires là, bien que pour être complet, je vais citer le fichier MyDB.desginer.cs:

[Table(Name="#temptab")] 
    public partial class TempTab : INotifyPropertyChanging, INotifyPropertyChanged 
    { 

      private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty); 

      private int _recno; 

#region Extensibility Method Definitions 
partial void OnLoaded(); 
partial void OnValidate(System.Data.Linq.ChangeAction action); 
partial void OnCreated(); 
partial void OnrecnoChanging(int value); 
partial void OnrecnoChanged(); 
#endregion 

      public TempTab() 
      { 
        OnCreated(); 
      } 

      [Column(Storage="_recno", DbType="Int NOT NULL", IsPrimaryKey=true)] 
      public int recno 
      { 
        get 
        { 
          return this._recno; 
        } 
        set 
        { 
          if ((this._recno != value)) 
          { 
            this.OnrecnoChanging(value); 
            this.SendPropertyChanging(); 
            this._recno = value; 
            this.SendPropertyChanged("recno"); 
            this.OnrecnoChanged(); 
          } 
        } 
      } 

      public event PropertyChangingEventHandler PropertyChanging; 

      public event PropertyChangedEventHandler PropertyChanged; 

      protected virtual void SendPropertyChanging() 
      { 
        if ((this.PropertyChanging != null)) 
        { 
          this.PropertyChanging(this, emptyChangingEventArgs); 
        } 
      } 

      protected virtual void SendPropertyChanged(String propertyName) 
      { 
        if ((this.PropertyChanged != null)) 
        { 
          this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
        } 
      } 
    } 

Ensuite, il est devenu simplement question de jongler avec certaines choses dans le code. Là où je serais normalement dû:

MyDBDataContext mydb = new MyDBDataContext(); 

je devais le faire pour partager sa connexion avec un SqlConnection normale pour que je puisse utiliser la connexion pour créer la table temporaire. Après cela, il semble tout à fait utilisable.

string connstring = "Data Source.... etc.."; 
SqlConnection conn = new SqlConnection(connstring); 
conn.Open(); 

SqlCommand cmd = new SqlCommand("create table #temptab " + 
           "(recno int primary key not null)", conn); 
cmd.ExecuteNonQuery(); 

MyDBDataContext mydb = new MyDBDataContext(conn); 
// Now insert some records (1 shown for example) 
TempTab tt = new TempTab(); 
tt.recno = 1; 
mydb.TempTabs.InsertOnSubmit(tt); 
mydb.SubmitChanges(); 

Et l'utiliser:

// Through normal SqlCommands, etc... 
cmd = new SqlCommand("select top 1 * from #temptab", conn); 
Object o = cmd.ExecuteScalar(); 

// Or through Linq 
var t = from tx in mydb.TempTabs 
     from v in mydb.v_BigTables 
     where tx.recno == v.recno 
     select tx; 

Quelqu'un voit-il un problème avec cette approche comme une solution polyvalente pour l'utilisation de tables temporaires dans les jointures dans LINQ?

Il a résolu mon problème à merveille, comme maintenant je peux faire une jointure directe dans Linq au lieu d'avoir à utiliser .Contains().

Postscript: Le seul problème que j'ai est que le mélange LINQ et SqlCommands réguliers sur la table (où l'on est de lecture/écriture et est donc l'autre) peut être dangereux. Toujours en utilisant SqlCommands à insérer sur la table, puis les commandes Linq pour lire cela fonctionne bien. Apparemment, Linq met en cache les résultats - il y a probablement un moyen de contourner le problème, mais ce n'était pas évident.

Répondre

3

Je ne vois pas de problème avec l'utilisation de tables temporaires pour résoudre votre problème. En ce qui concerne le mélange de SqlCommands et de LINQ, vous avez absolument raison quant au facteur de risque. Il est si facile d'exécuter vos instructions SQL en utilisant un DataContext, je ne vous inquiétez pas même sur le SqlCommand:

private string _ConnectionString = "<your connection string>"; 

public void CreateTempTable() 
{ 
    using (MyDBDataContext dc = new MyDBDataContext(_ConnectionString)) 
    { 
     dc.ExecuteCommand("create table #temptab (recno int primary key not null)"); 
    } 
} 

public void DropTempTable() 
{ 
    using (MyDBDataContext dc = new MyDBDataContext(_ConnectionString)) 
    { 
     dc.ExecuteCommand("DROP TABLE #TEMPTAB"); 
    } 
} 

public void YourMethod() 
{ 
    CreateTempTable(); 

    using (MyDBDataContext dc = new MyDBDataContext(_ConnectionString)) 
    { 
     ... 
     ... do whatever you want (within reason) 
     ... 
    } 

    DropTempTable(); 
} 
0

En tant que « solution polyvalente », si votre code est exécuté dans plus d'un fils/applications? Je pense que la solution des grandes listes est toujours liée au domaine du problème. Il est préférable d'utiliser une table standard pour le problème sur lequel vous travaillez.

J'ai déjà créé une table de liste "générique" dans la base de données. La table a été créée avec trois colonnes: int, uniqueidentifier et varchar, avec d'autres colonnes pour gérer chaque liste. Je pensais: "ça devrait suffire à gérer beaucoup de cas". Mais bientôt j'ai reçu une tâche qui nécessite une jointure avec une liste sur trois entiers.Après cela, je n'ai jamais essayé de créer une table de liste "générique" à nouveau.

De même, il est préférable de créer un SP pour insérer plusieurs éléments dans la table de liste dans chaque appel de base de données. Vous pouvez facilement insérer ~ 2000 éléments en moins de 2 db aller-retour. De cause, selon ce que vous faites, la performance peut ne pas avoir d'importance.

EDIT: oublié c'est une table temporaire et une table temporaire est par connexion, donc mon argument précédent sur les multi-threads n'était pas correct. Mais encore, ce n'est pas une solution générale, pour appliquer le schéma fixe.

1

Nous avons une situation similaire, et bien que cela fonctionne, le problème est que vous n'avez pas vraiment affaire à des requêtes, donc vous ne pouvez pas facilement utiliser ce "avec" LINQ. Ce n'est pas une solution qui fonctionne avec les chaînes de méthode. Notre solution finale était simplement de lancer ce que nous voulons dans une procédure stockée, et d'écrire des sélections dans cette procédure par rapport aux tables temporaires quand nous voulons ces valeurs. C'est un compromis, mais les deux sont des solutions de contournement. Au moins avec le proc stocké le concepteur va générer le code d'appel pour vous, et vous avez une implémentation en boîte noire, donc si vous avez besoin de faire d'autres réglages, vous pouvez le faire strictement dans la procédure, sans recompiler.

Dans un monde parfait, il y aura un futur support pour écrire des instructions Linq2Sql qui vous permettent de décrire l'utilisation des tables temporaires dans vos requêtes, évitez l'instruction IN SQL pour les scénarios complexes comme celui-ci.

0

La solution proposée par Neil fonctionnerait-elle réellement? Si c'est une table temporaire, et que chacune des méthodes crée et dispose son propre contexte de données, je ne pense pas que la table temporaire serait toujours là après que la connexion ait été abandonnée.

Même si c'était là, je pense que ce serait un domaine où vous supposez une fonctionnalité de la façon dont les requêtes et les connexions finissent par être rendues, et c'est l'un des gros problèmes avec linq à sql - vous ne savez pas quoi Les ingénieurs pourraient trouver de meilleures façons de faire les choses.

Je le ferais dans un proc stocké. Vous pouvez toujours renvoyer le jeu de résultats dans une table prédéfinie si vous le souhaitez.

+0

Pour être honnête, je n'ai pas testé la solution que j'ai fournie en utilisant des tables temporaires. Cela étant dit, la solution fonctionnera certainement en utilisant des tables "permanentes". En outre, la raison pour laquelle j'utilise la méthode DataContext.ExecuteCommand() est que l'instruction SQL n'est pas du tout gérée par le moteur LINQ ... ce que vous envoyez est ce qui est exécuté. –

Questions connexes