2010-11-18 32 views
11

Esto es fácil de realizar en TSQL, ¡pero estoy aquí sentado golpeando mi cabeza contra el escritorio tratando de hacer que funcione en EF4!Entity Framework con agregado LINQ para concatenar cadena?

Tengo una tabla, vamos a llamarla TestData. Tiene campos, por ejemplo: DataTypeID, Name, DataValue.

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

Quiero grupo de ID de datos/Nombre y concatenar DataValue en una cadena CSV. El resultado deseado debe contener -

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

Ahora, aquí es como yo estoy tratando de hacerlo -

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

El problema es que LINQ a Entidades no sabe cómo convertir esto en SQL. Esto es parte de una unión de 3 consultas LINQ, y realmente me gustaría que siga así. Me imagino que podría recuperar los datos y luego realizar el agregado más tarde. Por razones de rendimiento, eso no funcionaría para mi aplicación. También consideré usar una función de servidor SQL. Pero eso simplemente no parece "correcto" en el mundo de EF4.

¿Alguien tiene cuidado de echarle un vistazo a esto?

+0

Gracias, parece que si hubiera publicado esto como respuesta, sería el aceptado. Estoy bastante decepcionado, aunque no completamente sorprendido, esto no es posible. –

+0

En general, si no puede hacerlo en SQL (razonablemente estándar), probablemente no pueda hacerlo en L2E. El SQL estándar no tiene una forma de tomar una lista arbitrariamente larga y concatenarla en una cadena. Puede escribir una función SQL/proc para esto, y puede llamar con L2E, sin embargo. –

Respuesta

3

Gracias a moi_meme por la respuesta. Lo que esperaba hacer NO ES POSIBLE con LINQ to Entities. Como otros han sugerido, debe usar LINQ to Objects para tener acceso a los métodos de manipulación de cadenas.

Véase el enlace publicado por moi_meme para obtener más información - (Enlace Actualizado, gracias jnm2 por dejarme saber) http://www.purritos.com/blog/archives/4510

+0

Enlace muerto: consulte https://web.archive.org/web/20141106094131/http://www.mythos-rini.com/blog/archives/4510 – jnm2

0

Quizás sea una buena idea crear una vista para esto en la base de datos (que concatena los campos para usted) y luego hacer que EF use esta vista en lugar de la tabla original?

Estoy seguro de que no es posible en una declaración LINQ o en los detalles de mapeo.

0

Ya estás muy cerca. Prueba esto:

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

Como alternativa, puede hacer esto, si EF no permite la String.Join (que LINQ to SQL hace):

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

EF no es compatible con string.join y esperaba obtener esto en la consulta principal. Es bastante fácil si envuelvo con otra consulta. Esperaba que todo esto sucediera en el lado SQL, no en el lado del cliente. –

12

Si el ToList() es parte del original consulta y no sólo añadido para este ejemplo, a continuación, utilizar LINQ a objetos en la lista resultante de hacer la agregación:

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

Probado en LINQPad y produce este resultado:

alt text

+0

Mis disculpas, ToList() está al final de todo, debería haberlo dejado fuera del ejemplo. Tengo 3 consultas LINQ unidas y esperaba obtener este campo CSV en la consulta LINQ antes de ToList. Parece que esto no es posible según el enlace de moi_meme anterior. ¡Gracias por intentarlo! –

+0

Motivo de downvoting: "Me imagino que podría recuperar los datos y luego realizar el agregado más tarde. Por motivos de rendimiento, eso no funcionaría para mi aplicación". No respondiste la pregunta. – Pluc

+0

@Jeff Ogata esto es elegante. ¿Podrías explicar lo que está pasando? Especialmente la última línea? – Tigran

2

algunas de las respuestas sugerimos llamar ToList() y luego realizar el cálculo como LINQ al objeto. Eso está bien para poca cantidad de datos, pero si tengo una gran cantidad de datos que no quiero cargar en la memoria demasiado pronto, entonces, ToList() puede no ser una opción.

Tenía un requisito similar. Mi problema era obtener la lista de elementos secundarios de una entidad y crear una cadena de valores separados por comas con el primer carácter de ese elemento secundario.

  1. Creé una propiedad en mi modelo de vista que contendrá los datos brutos del repositorio.

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

Ok, de esa manera, mi modelo de vista estaba listo. Después de eso, cargué los Datos de LINQ a Entity a este Modelo de Vista fácilmente sin llamar a .ToList() que habría cargado todos los datos en la memoria. Si la base de datos tuviera miles de registros, nunca llamaría a .ToList().

Ejemplo:

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

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

Ahora, puedo llamar FormattedData propiedad de MyViewModel en cualquier momento cuando lo que necesito y el Getter se ejecutará solamente cuando la propiedad se llama.

Cuestiones relacionadas