需要帮助将SQL Server UNION语法转换为LINQ

时间:2010-08-19 04:19:29

标签: linq sql-server-2005 union

我有下面的SQL可以正常工作:

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

我正在尝试将其转换为LINQ,这不能正常工作:)显然,这里有很多错误 - 这只是一个粗略的开始。它没有考虑上面的临时列或order by条件,并且泛型/返回类型歧义是我尝试理解两种不同的返回类型:

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;
}

我遇到的问题是:

1)我在我的选择中处理两种不同的类型(警报和状态更新) 我不确定如何组合它们(或返回什么类型)。我猜这可能 用泛型来解决?

2)在我的SQL中,我有这样的代码:(SELECT 10 AS Expr1) AS AlertTypeId,它将值10添加到临时列AlertTypeId(允许联合将其与Alert的实际列AlertTypeId匹配)。如何在LINQ中完成这样的临时列/我该怎么做?

感谢您的帮助。

修改--------------------------------- EDIT --------- ---------------------------------修改

好的,我还有点进一步。以下是我目前的情况。您会注意到我添加了一些逻辑来返回朋友关系的更新。我还将此作为类型IList的通用方法,因为警报和更新必须是通用的才能达成一致。我在调用方法中传递StatusUpdate(下面进一步说明)。

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();

    }

}

而且,调用方法:

protected void LoadStatus()
{

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

    repStatusUpdates.DataBind();

}

这里是我用来通过LINQ访问我的Alert和StatusUpdate表的存储库的接口:

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);        
    }

当前问题:

1)当我编译这段代码时,我得到了这个奇怪的错误:

  

无法投射类型的对象   'System.Data.Linq.SqlClient.SqlNew'到   类型   'System.Data.Linq.SqlClient.SqlValue'。

我能找到的唯一读物是this link,虽然那里没有明确的解决方案(至少我可以说)。但是,如果上面的LINQ代码看起来不太好,也许你建议的任何内容都会导致此错误消失。

2)上面的代码仍未考虑原始SQL中的这一行:

(SELECT 10 AS Expr1) AS AlertTypeId

但这很小。

再次感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

试试这个(我将StatusUpdate转换为警报,如果这是不可接受的,您将不得不将警报转换为StatusUpdate,或者创建一个新类):

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();

我选择最后一次的原因是您不必为您未使用的所有结果构建新警报。

这将为您提供警报列表。

在这种情况下使用泛型有点难以实现。例如,你不能这样做:

IQueryable alerts =(来自_alerts                         其中a.AccountId == accountId                         选择a);

因为它隐式地将a转换为类型T.即使您尝试限制T实现或继承的内容:

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

您仍会遇到问题,因为无法静态地确切知道T的类型,因此您无法知道它是否可以从Alert和StatusUpdate转换。

另一种方法是明确使用IAlert作为返回类型:

public List<IAlert> GetSomething(Int32 accountId)

使用IAlert:

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

如果您同时使用Alert和StatusUpdate实现IAlert,则可以将其重写为:

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();

这是我将采用的路径,而不是传递一些未知类型并尝试限制它实现或继承的内容,因为转换为该类型可能仍然无效。

答案 1 :(得分:0)

你只能接受相同类型序列的联合。您需要将警报和更新转换为常见类型的序列,然后进行联合。您可以使用匿名类型执行此操作。如果类型没有任何共同点,则特别有用。

//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);

如果您有共同的字段,除了包含已知字段外,您仍然使用匿名类型。

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);

关键是两个匿名类型具有相同确切类型的完全相同的属性。然后你可以把他们两者结合起来。