Récupérer un object d’entitéframework sans UN champ

J’utilise un framework d’entités pour me connecter à la firebase database. J’ai un petit problème:

J’ai une table qui a une colonne varbinary (MAX) (avec filestream).

J’utilise la requête SQL pour gérer la partie “Data”, mais EF pour le rest (métadonnées du fichier).

J’ai un code qui doit obtenir tous les fichiers id, nom de fichier, guid, date de modification, … d’un fichier. Cela n’a pas du tout besoin du champ “Données”.

Existe-t-il un moyen de récupérer une liste sans que cette colonne ne soit remplie?

Quelque chose comme

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

??

Je sais que je peux créer des objects anonymes, mais je dois transmettre le résultat à une méthode, donc pas de méthodes anonymes. Et je ne veux pas mettre cela dans une liste de type anonyme, puis créer une liste de mon type non anonyme (Fichier).

Le but est d’éviter ceci:

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

(J’utilise ici le type anonyme car sinon vous obtiendrez une exception NotSupportedException: l’entité ou le type complexe ‘ProjectName.File’ ne peut pas être construit dans une requête LINQ to Entities.)

(par exemple, ce code lance l’exception précédente:

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

et “File” est le type que j’obtiens avec un context.Files.ToList() . Ceci est la bonne classe:

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

Le fichier est une classe connue de mon contexte de données EF:

 public ObjectSet Files { get { return _files ?? (_files = CreateObjectSet("Files")); } } private ObjectSet _files; 

Existe-t-il un moyen de récupérer une liste sans que cette colonne ne soit remplie?

Pas sans projection que vous voulez éviter. Si la colonne est mappée, elle fait naturellement partie de votre entité. Entité sans cette colonne n’est pas complète – il s’agit d’un dataset différent = projection.

J’utilise ici le type anonyme car sinon vous obtiendrez une exception NotSupportedException: l’entité ou le type complexe ‘ProjectName.File’ ne peut pas être construit dans une requête LINQ to Entities.

Comme exception, vous ne pouvez pas projeter sur une entité mappée. J’ai mentionné la raison ci-dessus – la projection fait en sorte que différents ensembles de données et que les EF n’aiment pas les «entités partielles».

Erreur 16 Erreur 3023: Problème lors du mappage des fragments à partir de la ligne 2717: Column Files.Data dans la table Les fichiers doivent être mappés: il n’a pas de valeur par défaut et n’est pas nullable.

Il ne suffit pas de supprimer la propriété du concepteur. Vous devez ouvrir EDMX en XML et supprimer également la colonne de SSDL, ce qui rendra votre modèle très fragile (chaque mise à jour depuis la firebase database mettra votre colonne en arrière). Si vous ne souhaitez pas mapper la colonne, vous devez utiliser la vue firebase database sans la colonne et mapper la vue au lieu de la table, mais vous ne pourrez pas insérer de données.

Pour contourner tous vos problèmes, utilisez le fractionnement de table et séparez la colonne binary problématique par une autre entité ayant une relation 1: 1 avec votre entité de File principale.

Je ferais quelque chose comme ça:

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

Thing est votre entité que EF sait matérialiser. L’instruction SQL résultante ne doit pas inclure la grande colonne dans son jeu de résultats, car elle n’est pas nécessaire dans la requête.

EDIT : à partir de vos modifications, vous obtenez l’erreur NotSupportedException: l’entité ou le type complexe ‘ProjectName.File’ ne peut pas être créé dans une requête LINQ to Entities. parce que vous n’avez pas cartographié cette classe en tant qu’entité. Vous ne pouvez pas inclure d’objects dans les requêtes LINQ to Entities que EF ne connaît pas et s’attendre à ce qu’il génère des instructions SQL appropriées.

Vous pouvez mapper un autre type qui exclut la VarBinary(MAX) dans sa définition ou utiliser le code ci-dessus.

tu peux le faire:

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

ou ca:

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

en fonction de votre version de EF

J’avais cette exigence parce que j’ai une entité Document qui a un champ Content avec le contenu du fichier, soit environ 100 Mo, et j’ai une fonction de recherche que je voulais retourner le rest des colonnes.

J’ai choisi d’utiliser la projection:

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

Ensuite, mon contrôleur WebApi transmet cet object de results à une fonction de pagination commune, qui applique un .Skip , .Take et un .ToList .

Cela signifie que lorsque la requête est exécutée, elle n’accède pas à la colonne Content , de sorte que les données de 100 Mo ne sont pas touchées et que la requête est aussi rapide que vous le souhaitez.

Ensuite, je le réinjecte dans ma classe DTO, qui, dans ce cas, est à peu près la même que la classe d’entité, ce n’est donc pas une étape à mettre en œuvre, mais elle suit mon modèle de codage WebApi typique:

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

Ensuite, je retourne la liste DTO:

 return Ok(dtos); 

Donc, il utilise la projection, qui peut ne pas correspondre aux exigences de l’affiche originale, mais si vous utilisez des classes DTO, vous convertissez quand même. Vous pourriez tout aussi bien faire ce qui suit pour les renvoyer en tant qu’entités réelles:

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

Juste quelques étapes supplémentaires, mais cela fonctionne très bien pour moi.

J’utilise ici le type anonyme car sinon vous obtiendrez une exception NotSupportedException: l’entité ou le type complexe ‘ProjectName.File’ ne peut pas être construit dans une requête LINQ to Entities.

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

Et aussi, ce n’est pas une mauvaise pratique de déstabiliser davantage la table, c’est-à-dire une avec les métadonnées et l’autre avec la charge utile pour éviter la projection. La projection fonctionnerait, le seul problème est que vous devez modifier chaque fois qu’une nouvelle colonne est ajoutée à la table.

J’aimerais partager mes tentatives pour résoudre ce problème au cas où quelqu’un d’autre serait dans la même situation.

J’ai commencé avec ce que Jeremy Danyow a suggéré, ce qui pour moi est l’option la moins pénible.

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

Dans mon cas, j’avais besoin d’un object de résultat IQueryable<> alors j’ai ajouté AsQueryable() à la fin. Cela m’a bien sûr permis d’append des appels à .Where , .Take , et aux autres commandes que nous connaissons tous, et ils ont bien fonctionné. Mais il y a une réserve:

Le code normal (essentiellement context.myEntity.AsQueryable() ) a renvoyé un object System.Data.Entity.DbSet , alors que cette approche System.Linq.EnumerableQuery .

Apparemment, cela signifie que ma requête personnalisée est exécutée “telle quelle” dès que nécessaire et que le filtrage que j’ai ajouté plus tard est effectué par la suite et non dans la firebase database.

Par conséquent, j’ai essayé d’imiter l’object Entity Framework en utilisant la requête exacte créée par EF, même avec les alias [Extent1] , mais cela n’a pas fonctionné. Lors de l’parsing de l’object résultant, sa requête s’est terminée comme

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

au lieu du prévu

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

En tout cas, cela fonctionne, et tant que la table n’est pas énorme, cette méthode sera assez rapide. Sinon, vous n’avez pas d’autre choix que d’append manuellement les conditions en concaténant des chaînes, comme le SQL dynamic classique. Un exemple très simple au cas où vous ne savez pas de quoi je parle:

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

Dans le cas où votre méthode nécessite parfois ce champ, vous pouvez append un paramètre bool , puis faire quelque chose comme ceci:

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

Si quelqu’un parvient à faire fonctionner correctement les extensions Linq comme s’il s’agissait de l’object EF d’origine, veuillez commenter pour que je puisse mettre à jour la réponse.

J’ai essayé ceci:

A partir du diagramme edmx (EF 6), j’ai cliqué sur la colonne que je voulais cacher d’EF et sur leurs propriétés, vous pouvez définir leur getter et leur setter sur private. De cette façon, pour moi ça marche.

Je retourne des données qui incluent une référence utilisateur, donc je voulais cacher le champ mot de passe même s’il est chiffré et salé, je ne le voulais pas sur mon json, et je ne voulais pas:

 Select(col => new {}) 

parce que c’est une douleur à créer et à entretenir, en particulier pour les grandes tables avec beaucoup de relations.

L’inconvénient de cette méthode est que si vous régénérez un modèle, vous devrez modifier à nouveau son getter et son setter.