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