Entity Framework с агрегатом LINQ для объединения строк? - PullRequest
14 голосов
/ 18 ноября 2010

Мне легко работать в 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. Это часть 3 запросов LINQ, и мне бы очень хотелось, чтобы все так и было. Я полагаю, что я мог бы получить данные, а затем выполнить агрегирование позже. По соображениям производительности, это не будет работать для моего приложения. Я также рассмотрел использование функции сервера SQL. Но это не кажется «правильным» в мире EF4.

Кто-нибудь хочет покончить с этим?

Ответы [ 5 ]

12 голосов
/ 18 ноября 2010

Если 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

5 голосов
/ 05 сентября 2014

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

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

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

    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 to Entity в эту модель представления, не вызывая .ToList (), который бы загружал все данные в память. Если бы в базе данных были тысячи записей, я бы никогда не вызвал .ToList ().

Пример:

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

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

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

3 голосов
/ 18 ноября 2010

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

Для получения дополнительной информации см. Ссылку, опубликованную moi_meme.

Обновление 8/27 /2018 - Обновленная ссылка (снова) - https://web.archive.org/web/20141106094131/http://www.mythos-rini.com/blog/archives/4510

И так как я беру flack за ответ только на ссылку от 8 лет назад, я уточню на случай, если архивная копия исчезнетдень.Основная суть в том, что вы не можете получить доступ к string.join в запросах EF.Вы должны создать запрос LINQ, а затем вызвать ToList (), чтобы выполнить запрос к базе данных.Затем у вас есть данные в памяти (также называемые LINQ to Objects), так что вы можете получить доступ к string.join.

Предлагаемый код по указанной выше ссылке выглядит следующим образом -

var result1 = (from a in users
                b in roles
           where (a.RoleCollection.Any(x => x.RoleId = b.RoleId))
           select new 
           {
              UserName = a.UserName,
              RoleNames = b.RoleName)                 
           });

var result2 = (from a in result1.ToList()
           group a by a.UserName into userGroup
           select new 
           {
             UserName = userGroup.FirstOrDefault().UserName,
             RoleNames = String.Join(", ", (userGroup.Select(x => x.RoleNames)).ToArray())
           });

Далее автор предлагает заменить string.join на агрегат для повышения производительности, например, -

RoleNames = (userGroup.Select(x => x.RoleNames)).Aggregate((a,b) => (a + ", " + b))
0 голосов
/ 18 ноября 2010

Вы уже очень близки. Попробуйте это:

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();
0 голосов
/ 18 ноября 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...