2010-08-19 9 views
0

J'ai le SQL ci-dessous qui fonctionne très bien:Besoin d'aide Translating SQL Server UNION syntaxe LINQ

SELECT  Message, CreateDate, AccountId, AlertTypeId 
FROM  dbo.Alerts 
UNION 
SELECT  TOP (100) PERCENT Status, CreateDate, AccountId, 
          (SELECT  10 AS Expr1) AS AlertTypeId 
FROM   dbo.StatusUpdates 
WHERE AccountId = PassedInParameter 
ORDER BY CreateDate DESC 

Je suis en train de le convertir en LINQ, qui ne fonctionne pas très bien :) De toute évidence, il Il y a beaucoup de mal ici - c'est juste un début difficile. Il ne tient pas compte de la colonne de température au-dessus ou la condition order by et l'ambiguïté des génériques/type de retour est ma tentative de comprendre les deux types de retour différents:

public List<T> GetSomething<T>(Int32 accountId) 
{ 
    List<T> result; 

    using (DataContext dc = _conn.GetContext()) 
    { 
     IEnumerable<Alert> alerts = (from a in dc.Alerts 
            where a.AccountId == accountId 
            select a); 
     IEnumerable<StatusUpdate> updates = (from s in dc.StatusUpdates 
              where s.AccountId == accountId 
              select s); 

     IEnumerable<T> obj = alerts.Union(updates); 

     result = obj.ToList(); 
    } 

    return result; 
} 

Les problèmes que je rencontrais sont:

1) J'ai affaire à deux types différents (Alertes et StatusUpdate) dans mes sélections et Je ne suis pas sûr de savoir comment les combiner (ou quel type retourner). Je devine que cela pourrait être résolu avec des génériques?

2) Dans mon SQL, j'ai ce code: (SELECT 10 AS Expr1) AS AlertTypeId qui ajoute la valeur dix à la colonne temp AlertTypeId (permettant à l'union de la faire correspondre à la vraie colonne AlertTypeId d'Alert). Comment les colonnes temporaires sont-elles accomplies dans LINQ/comment faire?

Merci pour votre aide.

EDIT --------------------------------- EDIT ---------- -------------------------------- EDIT

OK, je suis un peu plus loin. Voici ce que j'ai actuellement. Vous remarquerez que j'ai ajouté une certaine logique pour retourner les mises à jour pour les relations d'amis. J'ai également fait cela une méthode générique de type IList étant donné que les alertes et les mises à jour doivent être génériques pour être d'accord. Je passe dans StatusUpdate dans la méthode d'appel (plus bas ci-dessous).

public IList GetUpdatesByAccountId<T>(Int32 accountId) 
{ 
    List<Friend> friends = _friendRepository.GetFriendsByAccountId(accountId); 

    using (DataContext dc = _conn.GetContext()) 
    { 
     // Get all the account ids related to this user 
     var friendAccountIds = 
      friends.Select(friend => friend.MyFriendsAccountId).Distinct(); 
      friendAccountIds = friendAccountIds.Concat(new[] { accountId }); 

     var updates = 
      dc.StatusUpdates.Where(s => s.AccountId.HasValue && friendAccountIds.Contains(s.AccountId.Value)).Select(
       s => new { Alert = (Alert)null, StatusUpdate = s}); 

     var alerts = 
      dc.Alerts.Where(a => a.AccountId == accountId).Select(
       a => new {Alert = a, StatusUpdate = (StatusUpdate) null}); 

     var obj = updates.Union(alerts).Take(100); 

     return obj.OrderByDescending(su => su.StatusUpdate.CreateDate).ToList(); 

    } 

} 

Et, la méthode d'appel:

protected void LoadStatus() 
{ 

    repStatusUpdates.DataSource = _statusRepository 
     .GetUpdatesByAccountId<StatusUpdate>(_userSession.CurrentUser.AccountId); 

    repStatusUpdates.DataBind(); 

} 

et voici les interfaces aux référentiels que je utilise pour accéder à mon alerte et tables StatusUpdate via LINQ:

public interface IAlertRepository 
    { 
     List<Alert> GetAlertsByAccountId(Int32 accountId); 
     void SaveAlert(Alert alert); 
     void DeleteAlert(Alert alert); 
    } 

public interface IStatusUpdateRepository 
    { 
     StatusUpdate GetStatusUpdateById(Int32 statusUpdateId); 
     List<StatusUpdate> GetStatusUpdatesByAccountId(Int32 accountId); 
     List<StatusUpdate> GetFriendStatusUpdatesByAccountId(Int32 accountId, Boolean addPassedInAccount); 
     void SaveStatusUpdate(StatusUpdate statusUpdate); 
     List<StatusUpdate> GetTopNStatusUpdatesByAccountId(Int32 accountId, Int32 number); 
     List<StatusUpdate> GetTopNFriendStatusUpdatesByAccountId(Int32 accountId, Int32 number, Boolean addPassedInAccount);   
    } 

Problèmes actuels :

1) Lorsque je compile ce code, j'obtiens cette erreur étrange:La seule lecture que je peux trouver sur elle est this link bien qu'il n'y ait pas une solution claire là (au moins que je peux dire). Cependant, si le code LINQ ci-dessus ne vous convient pas, peut-être que ce que vous suggérez fera disparaître cette erreur.

2) Le code ci-dessus est toujours pas rendre compte de cette ligne de SQL d'origine:

(SELECT 10 AS Expr1) AS AlertTypeId 

mais cela est mineur.

Merci encore pour l'aide.

+0

Ce message d'erreur bizarre signifie probablement que votre requête LinqToSQL était trop compliquée pour LinqToSQL, même si compliquée que leurs tests ne tenaient pas compte de l'écriture de ce code. – erikkallen

+0

Je ne vois pas l'intérêt de faire de cette méthode une méthode générique. De plus, vous ne voulez pas faire l'union Take after you parce que ce n'est pas ce que vous faites dans la requête SQL. – Merritt

+0

Pas le plus grand fan de la création d'une liste de paires StatusUpdate/Alert où l'un d'eux est toujours nul. L'utilisation d'un type anonynonmique va ici limiter son utilité en dehors de la liaison de données et nécessitera potentiellement une réflexion pour trouver les valeurs de propriété. – Merritt

Répondre

1

Essayez ceci (i converti le si ce n'est pas acceptable, StatusUpdate à une alerte, vous allez devoir convertir soit l'alerte à un StatusUpdate, ou créer une nouvelle classe):

var alerts = (from a in dc.Alerts 
       where a.AccountId == accountId 
       select a); 
var updates = (from s in dc.StatusUpdates 
       where s.AccountId == accountId 
       select s) 
       .OrderByDescending(x => x.CreateDate) 
       .Take(100) 
       .Select(x => new Alert 
       { 
        Message = x.Percent.ToString(), 
        CreateDate = x.CreateDate, 
        AccountId = x.AccountId, 
        AlertTypeId = 10 // Is this right? 
       } 
       ); 

var obj = alerts.Union(updates); 

result = obj.ToList(); 

La raison pour laquelle je fais le dernier choix est que vous n'avez pas besoin de construire une nouvelle alerte pour tous les résultats que vous n'utilisez pas.

Cela vous donnera une liste d'alertes. L'utilisation d'un générique dans cette situation est difficile à retirer. Par exemple, vous ne pouvez pas faire ceci:

Alertes IQueryable = (d'un dans _alerts où a.AccountId == accountId sélectionnez a);

Parce que convertit implicitement pour taper T. Même si vous essayez de limiter ce T Matériel ou hérite de:

public List<T> GetSomething<T>(Int32 accountId) where T : IAlert// Interface that both StatusUpdates and IAlert implement 
public List<T> GetSomething<T>(Int32 accountId) where T : Alert 
public List<T> GetSomething<T>(Int32 accountId) where T : AlertBase // Base class for both Status and Alert 

Vous aurez toujours des difficultés à cause il n'y a aucun moyen de savoir exactement ce que statiquement le type T est, vous ne pouvez donc pas savoir s'il peut être converti à partir de Alert et StatusUpdate.

Une alternative est d'utiliser explicitement iAlert comme type de retour:

public List<IAlert> GetSomething(Int32 accountId) 

Avec iAlert:

public interface IAlert 
{ 
    int AccountId { get; set; } 
    int AlertTypeId { get; set; } 
    DateTime CreateDate { get; set; } 
    string Message { get; set; } 
} 

Si vous avez ont à la fois alerte et StatusUpdate mettre en œuvre iAlert, vous pouvez réécrire comme si :

IQueryable<IAlert> alerts = (from a in dc.Alerts 
       where a.AccountId == accountId 
       select a); 
IQueryable<IAlert> updates = (from s in dc.StatusUpdates 
       where s.AccountId == accountId 
       select s) 
       .OrderByDescending(x => x.CreateDate) 
       .Take(100); 

var obj = alerts.Union(updates); 

result = obj.ToList(); 

C'est la route que je prendrais au lieu de passer dans certains typ inconnu e et en essayant de limiter ce qu'il implémente ou hérite, car la conversion vers ce type peut être invalide.

+0

Bonjour Merritt. Merci. J'ai aimé votre code car il semblait répondre à toutes les choses que j'essayais d'accomplir dans mon SQL d'origine. Mais le compilateur se plaignait dans un certain nombre de points différents, à savoir avec les "nouvelles" déclarations. Il est super tard et je dois aller me coucher mais je vais le revoir demain et rapporter les erreurs que j'ai eu avec votre code. En attendant, j'ai posté mon code actuel mis à jour. Merci encore. –

+0

travaillant dessus ... maintenant au travail. – Merritt

+0

ouais, il suffit de retirer le nouveau devant le «a». Edited answer – Merritt

0

Vous pouvez uniquement prendre des unions de séquences de types égaux. Vous devez convertir les alertes et les mises à jour en séquences d'un type commun, puis prendre l'union. Vous pouvez le faire en utilisant des types anonymes. Particulièrement utile si les types n'ont rien en commun.

//this is a hack and probably not what you would want to use. 
var alerts = 
     from a in dc.Alerts 
     where a.AccountId == accountId 
     select new { Alert = a, StatusUpdate = (StatusUpdate)null }; 
var updates = 
     from s in dc.StatusUpdates 
     where s.AccountId == accountId 
     select new { Alert = (Alert)null, StatusUpdate = s }; 

//both are sequences of anonymous type with properties: 
// Alert (of type Alert) 
// StatusUpdate (of type StatusUpdate) 
var obj = alerts.Union(updates); 

Si vous avez des champs en commun, vous souhaitez toujours utiliser des types anonymes sauf que vous souhaitez inclure les champs connus.

var alerts = 
     from a in dc.Alerts 
     where a.AccountId == accountId 
     select new 
     { 
      a.Message, //assuming is a string 
      Status = (string)null, 
      a.CreateDate, 
      a.AccountId, 
      a.AlertTypeId //assuming is an int 
     }; 
var updates = 
     (from s in dc.StatusUpdates 
     where s.AccountId == accountId 
     select new 
     { 
      Message = (string)null, 
      s.Status, //assuming is a string 
      s.CreateDate, 
      s.AccountId, 
      AlertTypeId = 10 //this should handle the "10 AS AlertTypeId" part 
     }).OrderByDescending(s => s.CreateDate); 

var obj = alerts.Union(updates); 

La clé est que les deux types anonymes ont les mêmes propriétés exactes des mêmes types exacts. Ensuite, vous pouvez prendre l'union entre les deux.

+0

Bonjour Jeff. Merci. J'ai essayé de courir avec le ballon que vous avez lancé et j'ai posté mes modifications ci-dessus. J'ai aimé le code "champs communs" que vous avez posté car c'est ce que j'essaie de faire mais j'ai aussi besoin que s.Status et a.Message apparaissent comme une colonne (temp) (vous avez raison de dire qu'elles ne sont pas communes). Merci encore. –

+0

Je ne savais pas comment vous vouliez cette partie. J'ai mis à jour ma réponse. Le but est de rendre les types pour les deux ensembles exactement les mêmes. –

+0

Bonjour Jeff. Merci beaucoup. J'essaie de comprendre comment rendre cette méthode spécifique au type car cela a plus de sens pour ce que je fais mais j'ai des problèmes par ma réponse à Merritt. Si vous avez des idées supplémentaires sur la façon de le faire, veuillez me le faire savoir. Sinon, je rapporterai quand je comprendrai cela ... merci encore! –