Entity Framework с LINQ aggregate для объединения строки?

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

у меня есть таблица, назовем ее именем. Он имеет поля, скажем: 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 server. Но это просто не кажется "правильным" в мире EF4.

кто-нибудь хочет попробовать?

5 ответов


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

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


спасибо moi_meme за ответ. То, что я надеялся сделать, невозможно с LINQ to Entities. Как предлагали другие, вы должны использовать LINQ для объектов, чтобы получить доступ к методам манипуляции строками.

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

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

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

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

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

далее автор предлагает заменить строку.присоединяйтесь к aggregate для лучшей производительности, например -

RoleNames = (userGroup.Select(x => x.RoleNames)).Aggregate((a,b) => (a + ", " + b))

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

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

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

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

пример:

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

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

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


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

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


вы уже так близко. Попробуйте это:

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