2012-01-23 14 views
38

Estoy utilizando el marco de entidades para conectarme con la base de datos. Tengo un pequeño problema:Recuperar un objeto del marco de entidad sin un campo ONE

Tengo una tabla que tiene una columna varbinary (MAX) (con filestream).

Estoy usando una solicitud SQL para administrar la parte "Datos", pero EF para el resto (metadatos del archivo).

Tengo un código que tiene que obtener todos los archivos id, nombre de archivo, guid, fecha de modificación, ... de un archivo. Esto no necesita en absoluto el campo "Datos".

¿Hay alguna forma de recuperar una lista pero sin llenar esta columna?

Algo así como

context.Files.Where(f=>f.xyz).Exclude(f=>f.Data).ToList(); 

??

Sé que puedo crear objetos anónimos, pero necesito transmitir el resultado a un método, por lo que no hay métodos anónimos. Y no quiero poner esto en una lista de tipo anónimo, y luego crear una lista de mi tipo no anónimo (Archivo).

El objetivo es evitar esto:

using(RsSolutionsEntities context = new RsSolutionsEntities()) 
{ 
    var file = context.Files 
     .Where(f => f.Id == idFile) 
     .Select(f => new { 
      f.Id, f.MimeType, f.Size, f.FileName, f.DataType, 
      f.DateModification, f.FileId 
     }).FirstOrDefault(); 

    return new File() { 
     DataType = file.DataType, DateModification = file.DateModification, 
     FileId = file.FileId, FileName = file.FileName, Id = file.Id, 
     MimeType = file.MimeType, Size = file.Size 
    }; 
} 

(estoy usando aquí el tipo anónimo porque de lo contrario obtendrá un NotSupportedException: La entidad o tipo complejo 'ProjectName.File' no se puede construir en una . LINQ to Entities)

(por ejemplo, el código emitir la excepción anterior:

File file2 = context.Files.Where(f => f.Id == idFile) 
    .Select(f => new File() {Id = f.Id, DataType = f.DataType}).FirstOrDefault(); 

y "archivo" es el tipo de recibo con un context.Files.ToList(). Esta es la clase buena:

using File = MyProjectNamespace.Common.Data.DataModel.File; 

de archivos es una clase conocida de mi DataContext EF:

public ObjectSet<File> Files 
{ 
    get { return _files ?? (_files = CreateObjectSet<File>("Files")); } 
} 
private ObjectSet<File> _files; 
+3

¿Puedes quitar esa columna de tu objeto EF? – Gabe

+1

Desearía poder, pero es una columna "NON NULL", y EF no me gusta cuando tengo una columna no nula que no están en el modelo – J4N

+0

La única razón por la cual EF tendría un problema con las columnas no nulas excluidas es durante 'INSERT' a la base de datos. Puede solucionarlo utilizando procedimientos, activadores y otros métodos. Para 'SELECT', usted ** puede ** excluir columnas de la tabla. – Yuck

Respuesta

14

¿Hay alguna manera de recuperar una lista pero sin llenar esta columna?

No sin la proyección que desea evitar. Si la columna está mapeada, es parte natural de su entidad. La entidad sin esta columna no está completa; es un conjunto de datos diferente = proyección.

estoy usando aquí el tipo anónimo porque de lo contrario obtendrá un NotSupportedException : La entidad o tipo complejo 'ProjectName.File' no se puede construir en una consulta LINQ a Entidades.

Como dice la excepción, no se puede proyectar a una entidad mapeada. Mencioné la razón anterior: la proyección crea diferentes conjuntos de datos y a EF no les gustan las "entidades parciales".

Error 16 Error 3023: Problema en fragmentos de mapeo a partir de la línea 2717: Columna Files.Data en archivos de la tabla debe ser mapeada: No tiene ningún valor predeterminada y no es anulable.

No es suficiente eliminar la propiedad del diseñador. Debe abrir EDMX como XML y eliminar la columna de SSDL, lo que hará que su modelo sea muy frágil (cada actualización de la base de datos devolverá su columna). Si no desea asignar la columna, debe usar la vista de la base de datos sin la columna y asignar la vista en lugar de la tabla, pero no podrá insertar datos.

Como solución a todos sus problemas de consumo de table splitting y separar la columna binaria problemática a otra entidad con 1: 1 relación con su principal entidad File.

+8

Esta es una razón para abandonar EF. Absolutamente loco, esto es lo que más nos viene de la cabeza, excluyendo una sola gran columna que se adapta PERFECTAMENTE a las soluciones normales de bases de datos como una sola columna máxima, mientras que el resto se puede usar para enumerar esos objetos y demás.Las soluciones de la forma actual aumentan enormemente la complejidad, ya sea añadiendo todo tipo de tipos adicionales para hacer proyecciones, así como el código para convertir exactamente el mismo tipo en el mismo y viceversa; o haciendo una tabla 1: 1 y todo eso cuando no sea necesario. –

+0

¿Por qué queremos evitar las proyecciones? ¿Es por motivos de rendimiento? – Gilles

+0

@Gilles: Lo siento, es demasiado duro. No quería sugerir que la proyección sea mala. La proyección es genial si conoces las consecuencias. La consecuencia es que la proyección no es entidad: EF no proporcionará ninguna de sus características avanzadas cuando regrese la proyección. Es posible que la carga ansiosa no funcione (dependiendo de la consulta), la carga lenta no funcionará, el cambio de seguimiento no funcionará, etc. –

8

me gustaría hacer algo como esto:

var result = from thing in dbContext.Things 
      select new Thing { 
       PropertyA = thing.PropertyA, 
       Another = thing.Another 
       // and so on, skipping the VarBinary(MAX) property 
      }; 

Dónde Thing es su entidad que EF sabe cómo materializar. La instrucción SQL resultante no debe incluir la columna grande en su conjunto de resultados, ya que no es necesaria en la consulta.

EDITAR: A partir de las modificaciones, se obtiene el error NotSupportedException : La entidad o tipo complejo 'ProjectName.File' no pueden ser construidos en una LINQ a Entidades consulta. porque no ha asignado esa clase como una entidad. Usted no puede incluir objetos en las consultas de LINQ to Entities que EF desconoce y espera que genere sentencias de SQL apropiadas.

Puede asignar otro tipo que excluya la columna VarBinary(MAX) en su definición o utilice el código anterior.

+1

Ya lo intenté, pero EF me dice que no puedo poner un tipo complejo en la selección. – J4N

+0

¿Puedes publicar el código que probaste y el error que obtuviste en tu pregunta anterior? Parece que estás tratando de usar un objeto de un tipo que EF no conoce y no puede generar declaraciones SQL para. – Yuck

+0

He editado mi código anterior – J4N

5

usted puede hacer esto:

var files = dbContext.Database.SqlQuery<File>("select FileId, DataType, MimeType from Files"); 

o esto:

var files = objectContext.ExecuteStoreQuery<File>("select FileId, DataType, MimeType from Files"); 

dependiendo de su versión de EF

+0

si tiene un contexto dbcontext en lugar de un objeto, puede acceder al contexto del objeto todavía. (dbContext como IObjectContextAdapter) .ObjectContext; También hay un método disponible en dbcontext dbContext.Database.SqlQuery (string sql); –

+0

Esto funcionó para mí. Sin embargo, debe tenerse en cuenta que agregué una columna nula para el campo de datos vacío. "SELECT Id, Nombre, Ruta, CreateDate, LastUpdated, datos AS nulos FROM [Archivo]"; – Rhyous

0

me gustaría compartir mis intentos para solucionar este problema en el caso alguien más está en la misma situación.

Empecé con lo que Jeremy Danyow sugirió, que para mí es la opción menos dolorosa.

// You need to include all fields in the query, just make null the ones you don't want. 
var results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName"); 

En mi caso, necesitaba un objeto IQueryable<> resultado por lo que añade AsQueryable() al final. Esto, por supuesto, permítanme agregar llamadas al .Where, .Take, y los demás comandos que todos conocemos, y funcionaron bien. Pero hay una advertencia:

El código normal (básicamente context.myEntity.AsQueryable()) devolvió System.Data.Entity.DbSet<Data.DataModel.myEntity>, mientras que este enfoque devolvió System.Linq.EnumerableQuery<Data.DataModel.myEntity>.

Aparentemente, esto significa que mi consulta personalizada se ejecuta "tal cual" tan pronto como sea necesario y el filtrado que agregué más tarde se realiza después y no en la base de datos.

Por lo tanto, intenté imitar el objeto de Entity Framework utilizando la consulta exacta que EF crea, incluso con esos alias [Extent1], pero no funcionó.Al analizar el objeto resultante, su consulta terminado así

FROM [dbo].[TableName] AS [Extent1].Where(c => ...

en lugar del esperado

FROM [dbo].[TableName] AS [Extent1] WHERE ([Extent1]...

De todos modos, esto funciona, y siempre y cuando la mesa no es enorme, este método ser lo suficientemente rápido De lo contrario, no tiene más opción que agregar manualmente las condiciones mediante la concatenación de cadenas, como el clásico SQL dinámico. Un ejemplo muy básico en caso de que no saben lo que estoy hablando:

string query = "SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName"; 
if (parameterId.HasValue) 
    query += " WHERE Field1 = " + parameterId.Value.ToString(); 
var results = context.Database.SqlQuery<myEntity>(query); 

En caso de que su método a veces necesita este campo, se puede añadir un parámetro bool y luego hacer algo como esto:

IQueryable<myEntity> results; 
if (excludeBigData) 
    results = context.Database.SqlQuery<myEntity>("SELECT Field1, Field2, Field3, HugeField4 = NULL, Field5 FROM TableName").AsQueryable(); 
else 
    results = context.myEntity.AsQueryable(); 

Si alguien logra hacer que las extensiones de Linq funcionen correctamente como si fuera el objeto EF original, coméntelo para poder actualizar la respuesta.

1

tuve este requisito porque tengo una entidad Document que tiene un campo Content con el contenido del archivo, es decir, algunos de 100 MB de tamaño, y tengo una función de búsqueda que quería devolver el resto de las columnas.

I eligieron utilizar de proyección:

IQueryable<Document> results = dbContext.Documents.Include(o => o.UploadedBy).Select(o => new { 
    Content = (string)null, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    FileName = o.FileName, 
    Id = o.Id, 
    // etc. even with related entities here like: 
    UploadedBy = o.UploadedBy 
}); 

Entonces mi controlador WebAPI pasa este objeto results a una función Paginación común, que se aplica un .Skip, .Take y una .ToList.

Esto significa que cuando se ejecuta la consulta, no tiene acceso a la columna Content, por lo que no se tocan los 100MB de datos, y la consulta es tan rápida como se desea/se espera que sea.

A continuación, lo vuelvo a lanzar a mi clase DTO, que en este caso es exactamente igual a la clase de entidad, por lo que puede no ser un paso que necesite implementar, pero sigue mi patrón típico de codificación WebApi , por lo que:

var dtos = paginated.Select(o => new DocumentDTO 
{ 
    Content = o.Content, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    FileName = o.FileName, 
    Id = o.Id, 
    UploadedBy = o.UploadedBy == null ? null : ModelFactory.Create(o.UploadedBy) 
}); 

Entonces vuelvo la lista DTO:

return Ok(dtos); 

Por lo tanto, utiliza la proyección, que podría no ajustarse a los requisitos de su creador original, pero si usted está utilizando clases DTO, tu eres convirtiendo de todos modos. Se podía hacer con la misma facilidad a la siguiente devolverlos como sus entidades reales:

var dtos = paginated.Select(o => new Document 
{ 
    Content = o.Content, 
    ContentType = o.ContentType, 
    DocumentTypeId = o.DocumentTypeId, 
    //... 

A pocos pasos adicionales, pero esto está funcionando muy bien para mí.

0

yo probamos este:

A partir del diagrama edmx (EF 6), que hace clic en la columna que quería esconderse de EF y en sus propiedades que puede establecer su get y set a lo privado. De esa manera, para mí funciona.

Devuelvo algunos datos que incluyen una referencia de usuario, así que quería ocultar el campo de contraseña a pesar de que está encriptado y salado, simplemente no lo quería en mi JSON, y no quería hacer una:

Select(col => new {}) 

porque es un dolor crear y mantener, especialmente para las tablas grandes con muchas relaciones.

La desventaja de este método es que si alguna vez regeneras tu modelo, necesitarías modificar su getter y setter nuevamente.

0

estoy usando aquí el tipo anónimo porque de lo contrario obtendrá un NotSupportedException : La entidad o tipo complejo 'ProjectName.File' no se puede construir en una consulta LINQ a Entidades.

var file = context.Files 
     .Where(f => f.Id == idFile) 
     .FirstOrDefault() // You need to exeucte the query if you want to reuse the type 
     .Select(f => new { 
      f.Id, f.MimeType, f.Size, f.FileName, f.DataType, 
      f.DateModification, f.FileId 
     }).FirstOrDefault(); 

Y también que no es una mala práctica de de-normalizar la tabla en más, es decir uno con metadatos y otra con carga útil para evitar la proyección. La proyección funcionaría, el único problema es la necesidad de editar cada vez que se agrega una nueva columna a la tabla.

Cuestiones relacionadas