Нужна помощь в переводе синтаксиса SQL Server UNION в LINQ - PullRequest
0 голосов
/ 19 августа 2010

У меня есть приведенный ниже 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) Я имею дело с двумя различными типами (Alerts и StatusUpdate) в моих выборках, и я не уверен, как их объединить (или какой тип вернуть).Я предполагаю, что это может быть решено с помощью обобщений?

2) В моем SQL у меня есть этот код: (SELECT 10 AS Expr1) AS AlertTypeId, который добавляет значение десять к временному столбцу AlertTypeId (позволяя объединению сопоставить его с реальным Alertстолбец AlertTypeId).Как временные столбцы, подобные этому, выполняются в LINQ / как мне это сделать?

Спасибо за помощь.

РЕДАКТИРОВАТЬ --------------------------------- EDIT ------------------------------------------ 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();

}

И вот интерфейсы к репозиториям, которые я использую дляполучить доступ к моим таблицам предупреждений и StatusUpdate через 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);        
    }

Текущие проблемы:

1) Когда я компилирую этот код, я получаю странную ошибку:

Невозможно привести объект типа 'System.Data.Linq.SqlClient.SqlNew' к типу 'System.Data.Linq.SqlClient.SqlValue'.

Единственное чтение, которое я могу найти, это эта ссылка хотя там нет четкого решения (по крайней мере, я могу сказать).Однако если приведенный выше код LINQ вам не подходит, возможно, все, что вы предложите, приведет к исчезновению этой ошибки.

2) Приведенный выше код все еще не учитывает эту строку из исходного SQL:

(SELECT 10 AS Expr1) AS AlertTypeId

, но это незначительно.

Еще раз спасибо за помощь.

Ответы [ 2 ]

1 голос
/ 19 августа 2010

Попробуйте (я преобразовал StatusUpdate в предупреждение, если это неприемлемо, вам придется либо преобразовать Alert в 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 где a.AccountId == accountId выберите а);

Потому что это неявно преобразует 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();

Этот маршрут я бы выбрал вместо того, чтобы передавать какой-то неизвестный тип и пытаться ограничить то, что он реализует или наследует, потому что приведение к этому типу все еще может быть недопустимым.

0 голосов
/ 19 августа 2010

Вы можете принимать только объединения последовательностей одинакового типа.Вам необходимо преобразовать оповещения и обновления в последовательности общего типа, а затем принять объединение.Вы можете сделать это, используя анонимные типы.Особенно полезно, если типы не имеют ничего общего.

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

Ключ в том, что оба анонимных типа имеют одинаковые точные свойства одинаковых точных типов.Тогда вы можете взять союз между ними.

...