Entity Framework с агрегацией LINQ для конкатенации строки?

Это легко для меня выполнить в TSQL, но я просто сижу здесь, стуча себя в спину, пытаясь заставить его работать в EF4!

У меня есть таблица, давайте назовем ее TestData. Он имеет поля, например: DataTypeID, Name, DataValue.

DataTypeID, Name, DataValue
1,"Data 1","Value1"
1,"Data 1","Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

Я хочу сгруппировать по DataID/Name и объединить DataValue в строку CSV. Желаемый результат должен содержать -

DataTypeID, Name, DataValues
1,"Data 1","Value1,Value2"
2,"Data 1","Value3"
3,"Data 1","Value4"

Теперь, вот как я пытаюсь это сделать -

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = (string)g.Aggregate("", (a, b) => (a != "" ? "," : "") + b.DataValue),
 }).ToList()

Проблема в том, что LINQ to Entities не знает, как преобразовать это в SQL. Это часть объединения трех запросов LINQ, и мне бы очень хотелось, чтобы это было так. Я предполагаю, что я мог бы получить данные, а затем выполнить агрегат позже. По соображениям производительности это не сработает для моего приложения. Я также рассмотрел использование функции SQL-сервера. Но это просто не кажется "правильным" в мире EF4.

Кто-нибудь может взломать это?

Ответ 1

Спасибо moi_meme за ответ. То, что я надеялся сделать, НЕ ВОЗМОЖНО с LINQ для Entities. Как и другие, вы должны использовать LINQ to Objects для получения доступа к методам манипулирования строкой.

См. ссылку, опубликованную moi_meme для получения дополнительной информации - (Обновлено Ссылка, спасибо jnm2 за то, что дайте мне знать) http://www.purritos.com/blog/archives/4510

Ответ 2

Если ToList() является частью вашего исходного запроса, а не просто добавлен для этого примера, используйте LINQ to Objects в результирующем списке, чтобы выполнить агрегацию:

var query = (from t in context.TestData
            group t by new { DataTypeID = t.DataTypeID, Name = t.Name } into g 
            select new { DataTypeID = g.Key.DataTypeID, Name = g.Key.Name, Data = g.AsEnumerable()})
            .ToList()
            .Select (q => new { DataTypeID = q.DataTypeID, Name = q.Name, DataValues = q.Data.Aggregate ("", (acc, t) => (acc == "" ? "" : acc + ",") + t.DataValue) });

Протестировано в LINQPad, и он производит этот результат:

alt text

Ответ 3

В некоторых ответах предлагается вызвать ToList(), а затем выполнить вычисление как LINQ to OBJECT. Thats штраф за небольшое количество данных, но если у меня есть огромное количество данных, что я не хочу загружать в память слишком рано, то ToList() может не быть вариантом.

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

  • Я создал свойство в моей модели просмотра, в котором будут храниться исходные данные из репозитория.

    public class MyViewModel
    {
        public string AnotherRegularProperty { get; set; }
    
        public IEnumerable<string> RawChildItems { get; set; }
    
        public string FormattedData
        {
            get
            {
                if (this.RawChildItems == null)
                    return string.Empty;
    
                string[] theItems = this.RawChildItems.ToArray();
    
                return theItems.Length > 0
                    ? string.Format("{0} ( {1} )", this.AnotherRegularProperty, String.Join(", ", theItems.Select(z => z.Substring(0, 1))))
                    : string.Empty;
            }
        }
    }
    

Итак, моя ViewModel была готова. После этого я загрузил данные из LINQ в Entity в эту модель просмотра без вызова .ToList(), который загрузил бы все данные в память. Если в базе данных были thoudands записей, я бы никогда не вызывал .ToList().

Пример:

IQueryable<MyEntity> myEntities = _myRepository.GetData();

IQueryable<MyViewModel> viewModels = myEntities.Select(x => new MyViewModel() { AnotherRegularProperty = x.AProperty, RawChildItems = x.MyChildren })

Теперь я могу вызвать свойство FormattedData MyViewModel в любое время, когда мне нужно, и Getter будет выполняться только при вызове свойства.

Ответ 4

Может быть, неплохо создать представление для этого в базе данных (которое объединяет поля для вас), а затем заставить EF использовать это представление вместо исходной таблицы?

Я уверен, что это невозможно в инструкции LINQ или в параметрах Mapping.

Ответ 5

Ты уже очень близко. Попробуйте следующее:

var query = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = String.Join(",", g),
 }).ToList()

В качестве альтернативы вы можете сделать это, если EF не разрешает String.Join (который выполняется Linq-to-SQL):

var qs = (from t in context.TestData
  group h by new { DataTypeID = h.DataTypeID, Name = h.Name } into g
  select new
 {
   DataTypeID = g.Key.DataTypeID,
   Name = g.Key.Name,
   DataValues = g
 }).ToArray();

var query = (from q in qs
            select new
            {
                q.DataTypeID,
                q.Name,
                DataValues = String.Join(",", q.DataValues),
            }).ToList();