70

I'm using entity framework to connect with the database. I've one little problem:

I've one table which have one varbinary(MAX) column(with filestream).

I'm using SQL request to manage the "Data" part, but EF for the rest(metadata of the file).

I've one code which has to get all files id, filename, guid, modification date, ... of a file. This doesn't need at all the "Data" field.

Is there a way to retrieve a List but without this column filled?

Something like

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

??

I know I can create anonymous objects, but I need to transmit the result to a method, so no anonymous methods. And I don't want to put this in a list of anonymous type, and then create a list of my non-anonymous type(File).

The goal is to avoid this:

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

(I'm using here the anonymous type because otherwise you will get a NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query.)

(e.g. this code throw the previous exception:

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

and "File" is the type I get with a context.Files.ToList(). This is the good class:

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

File is a known class of my EF datacontext:

public ObjectSet<File> Files
{
    get { return _files  ?? (_files = CreateObjectSet<File>("Files")); }
}
private ObjectSet<File> _files;
Mark Kram
  • 5,672
  • 7
  • 51
  • 70
J4N
  • 19,480
  • 39
  • 187
  • 340
  • 3
    Can you just remove that column from your EF object? – Gabe Jan 23 '12 at 14:57
  • 1
    I wish I can but it's a "NON NULL" column, and EF doesn't like when I've non-null column which aren't in the model – J4N Jan 23 '12 at 15:05
  • The only reason EF would have a problem with excluded non-null columns is during `INSERT` to the database. You can work around that using procedures, triggers, and other methods. For `SELECT` you absolutely **can** exclude table columns. – Yuck Jan 23 '12 at 15:42
  • 1
    I tried, but since I remove the "Data" field, even I don't do any "Insert" with anywhere, I got an exception in my edmx model: Error 16 Error 3023: Problem in mapping fragments starting at line 2717:Column Files.Data in table Files must be mapped: It has no default value and is not nullable. – J4N Jan 23 '12 at 15:52
  • Take a look here - http://stackoverflow.com/questions/3274241/entity-framework-4-selective-lazy-loading-properties and http://www.deveducate.com/blog/post/2010/12/14/Entity-Framework-Modeling-Table-Splitting.aspx – Vladislav Zorov Jan 23 '12 at 15:57
  • 3
    Can you edit the actual database? It might be worth moving the Data column to its own table. – cadrell0 Jan 23 '12 at 16:10
  • Most of my fields are already in another table(CatalogElement) where I've all business informations. I only have things like "Filename" or size in this table. – J4N Jan 24 '12 at 06:25
  • I loved your *.Exclude() extension method idea... I am wondering if this solution is possible – juanora Mar 01 '18 at 07:38
  • Similar question for EF Core-[Exclude a column from a select using LINQ](//stackoverflow.com/q/19463099) – Michael Freidgeim Mar 29 '18 at 11:13

9 Answers9

22

Is there a way to retrieve a List but without this column filled?

Not without projection which you want to avoid. If the column is mapped it is natural part of your entity. Entity without this column is not complete - it is different data set = projection.

I'm using here the anonymous type because otherwise you will get a NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query.

As exception says you cannot project to mapped entity. I mentioned reason above - projection make different data set and EF don't like "partial entities".

Error 16 Error 3023: Problem in mapping fragments starting at line 2717:Column Files.Data in table Files must be mapped: It has no default value and is not nullable.

It is not enough to delete property from designer. You must open EDMX as XML and delete column from SSDL as well which will make your model very fragile (each update from database will put your column back). If you don't want to map the column you should use database view without the column and map the view instead of the table but you will not be able to insert data.

As a workaround to all your problems use table splitting and separate the problematic binary column to another entity with 1 : 1 relation to your main File entity.

Community
  • 1
  • 1
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • 17
    This is a reason for ditching EF. Absolutely insane, this is as bread and butter as things come, excluding a single big column that PERFECTLY FITS in normal database solutions as a single max column, while all the rest can be used in listing those objects and so forth. The solutions the current way greatly increase the complexity -- whether its adding all kinds of extra types to do projections as well as the code to convert the exact same type to the same one and back again; or, making a 1:1 table and all that when it is not needed. – Nicholas Petersen Jun 17 '14 at 21:40
  • Why do we want to avoid projections? Is it for performance reasons? – Gilles Feb 15 '17 at 20:25
  • @Gilles: Sorry, it is too harsh. I didn't want to suggest that projection is bad. Projection is great if you know the consequences. Consequence is that projection is not entity - EF will not provide any of its advanced features when you return projection. Eager loading may not work (depending on the query), lazy loading will not work, change tracking will not work, etc. – Ladislav Mrnka Feb 20 '17 at 14:37
17

I'd do something like this:

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

Where Thing is your entity that EF knows how to materialize. The resulting SQL statement shouldn't include the large column in its result set, since it's not needed in the query.

EDIT: From your edits, you get the error NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query. because you haven't mapped that class as an entity. You can't include objects in LINQ to Entities queries that EF doesn't know about and expect it to generate appropriate SQL statements.

You can map another type that excludes the VarBinary(MAX) column in its definition or use the code above.

Yuck
  • 49,664
  • 13
  • 105
  • 135
  • 2
    I already tried, but EF tells me that I can't put a complex type in the select. – J4N Jan 23 '12 at 15:04
  • Can you post the code you tried and the error you got in your question above? Sounds like you're trying to use an object of a type that EF doesn't know about and can't generate SQL statements for. – Yuck Jan 23 '12 at 15:06
  • But EF knows those files, my ObjectContext has EntitySet of it. And like I said, it gaves me a List if I do a context.Files.ToList(); – J4N Jan 23 '12 at 15:48
  • In the code above you're making a distinction between `ProjectName.File` and `MyProjectNamespace.Common.Data.DataModel.File`. It sounds like `ProjectName.File` is **not** mapped to EF, but the other is. – Yuck Jan 23 '12 at 15:50
  • Not at all, it just that in one error message, it use the assembly name, and in another place the full namespace. I only have one File in my project, I can guarante this – J4N Jan 23 '12 at 19:32
  • This and the one below worked for me. I like this one better though, as I don't have to include a SQL query as a string. – Rhyous Jun 15 '16 at 14:57
  • You *can* include anonymous objects – CervEd Dec 09 '20 at 10:09
10

you can do this:

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

or this:

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

depending on your version of EF

Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • if you have a dbcontext instead of an objectcontext you can access the object context still. (dbContext as IObjectContextAdapter).ObjectContext; Also there is a method available on the dbcontext dbContext.Database.SqlQuery(string sql); – Adam Heeg Aug 05 '15 at 14:38
  • 3
    This worked for me. However, it should be noted that I had add a null column for the empty Data field. "SELECT Id, Name, Path, CreateDate, LastUpdated, null AS Data FROM [File]"; – Rhyous Jun 14 '16 at 16:48
7

I had this requirement because I have a Document entity which has a Content field with the content of the file, i.e. some 100MB in size, and I have a Search function that I wanted to return the rest of the columns.

I chose to use projection:

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

Then my WebApi controller passes this results object to a common Pagination function, which applies a .Skip, .Take and a .ToList.

This means that when the query gets executed, it doesn't access the Content column, so the 100MB data is not being touched, and the query is as fast as you'd want/expect it to be.

Next, I cast it back to my DTO class, which in this case is pretty much exactly the same as the entity class, so this might not be a step you need to implement, but it's follows my typical WebApi coding pattern, so:

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

Then I return the DTO list:

return Ok(dtos);

So it uses projection, which might not fit the original poster's requirements, but if you're using DTO classes, you're converting anyway. You could just as easily do the following to return them as your actual entities:

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

Just a few extra steps but this is working nicely for me.


UPDATE: Currently I'm using an extension method so I don't have to maintain the list of fields in multiple places where I access this entity. So I have:

public static IQueryable<Document> SelectExcludingContent(this IQueryable<Document> query)
{
    return query.Select(o => new Document { DocumentId = o.DocumentId, FileName = o.FileName, ItemId = o.ItemId, Notes = o.Notes });
}

And then I use it like so:

IQueryable<Document> results = db.Documents
    .SelectExcludingContent(); 

Note this doesn't use the DTO, but it does mean you can't include other entities...

Sean
  • 14,359
  • 13
  • 74
  • 124
7

For EF Core 2 I implemented a solution like this:

var files = context.Files.AsNoTracking()
                         .IgnoreProperty(f => f.Report)
                         .ToList();

The base idea is to turn for example this query:

SELECT [f].[Id], [f].[Report], [f].[CreationDate]
FROM [File] AS [f]

into this:

SELECT [f].[Id], '' as [Report], [f].[CreationDate]
FROM [File] AS [f]

you can see the full source code in here: https://github.com/aspnet/EntityFrameworkCore/issues/1387#issuecomment-495630292

HamedH
  • 2,814
  • 1
  • 26
  • 37
1

I'd like to share my attempts to workaround this problem in case somebody else is in the same situation.

I started with what Jeremy Danyow suggested, which to me is the less painful option.

// 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");

In my case, I needed a IQueryable<> result object so I added AsQueryable() at the end. This of course let me add calls to .Where, .Take, and the other commands we all know, and they worked fine. But there's a caveat:

The normal code (basically context.myEntity.AsQueryable()) returned a System.Data.Entity.DbSet<Data.DataModel.myEntity>, while this approach returned System.Linq.EnumerableQuery<Data.DataModel.myEntity>.

Apparently this means that my custom query gets executed "as is" as soon as needed and the filtering I added later is done afterwards and not in the database.

Therefore I tried to mimic Entity Framework's object by using the exact query EF creates, even with those [Extent1] aliases, but it didn't work. When analyzing the resulting object, its query ended like

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

instead of the expected

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

Anyway, this works, and as long as the table is not huge, this method will be fast enough. Otherwise you have no option than to manually add the conditions by concatenating strings, like classic dynamic SQL. A very basic example in case you don't know what I'm talking about:

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

In case your method sometimes needs this field, you can add a bool parameter and then do something like this:

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

If anyone manages to make the Linq extensions work properly like if it was the original EF object, please comment so I can update the answer.

Community
  • 1
  • 1
Andrew
  • 7,602
  • 2
  • 34
  • 42
1

I'm using here the anonymous type because otherwise you will get a NotSupportedException: The entity or complex type 'ProjectName.File' cannot be constructed in a LINQ to Entities query.

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

And also its not a bad practice to de-normalize the table into further, i.e one with metadata and one with payload to avoid projection. Projection would work, the only issue is, need to edit any time a new column is added to the table.

skjagini
  • 3,142
  • 5
  • 34
  • 63
0

I tried this:

From the edmx diagram (EF 6), I clicked the column I wanted to hide from EF and on their properties you can set their getter and setter to private. That way, for me it works.

I return some data which includes a User reference, so I wanted to hide the Password field even though it's encrypted and salted, I just didn't want it on my json, and I didn't want to do a:

Select(col => new {}) 

because that's a pain to create and maintain, especially for big tables with a lot of relationships.

The downside with this method is that if you ever regenerate your model, you would need to modify their getter and setter again.

mikesoft
  • 741
  • 6
  • 14
0

Using Entity Framework Power Tools you can do the following in efpt.config.json:

"Tables": [
  {
     "ExcludedColumns": [
        "FileData"
     ],
     "Name": "[dbo].[Attachment]",
     "ObjectType": 0
  }
]
mr.coffee
  • 962
  • 8
  • 22