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;
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.
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.
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
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.
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
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.
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.
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.
Using Entity Framework Power Tools you can do the following in efpt.config.json:
"Tables": [
{
"ExcludedColumns": [
"FileData"
],
"Name": "[dbo].[Attachment]",
"ObjectType": 0
}
]
Related
I have an application that is using MVC framework that currently reads from a model object of a SQL table and puts the results in a list format. The table has its own .cs class with all the necessary fields and the code filters the results based on other factors.
The problem I am running into, is that I need to find a way to add new tables to this list without making changes to the code itself. Ideally, I would like to add a list of tables I need to read from into the web.config and create a class for them in the project structure and the code will dynamically read from that.
Currently with one table the code looks like this:
var assets = _model.FMIF.ToList();
var results = BuildFormatedResult(assets);
And I have been able to add another table to this structure like this:
var assets = _model.FMIF.ToList();
var assets2 = _model.FMHZ.ToList();
assets = assets.Concat(assets2).ToList();
var results = BuildFormatedResult(assets);
I have tried to make this dynamic using a method like this:
var test = "FMIF";
var assets = _model.+test+.ToList();
var results = BuildFormatedResult(assets);
but it does not appear to be able to read the variable as a model.
Is there a best practice way to do something like this? Even if it is way different than what I have tried I am kind of at a loss here. Not super familiar with MVC structure so any help or advice is greatly appreciated.
Thanks!
This is an approach I've used to dynamically get a handle to a DbSet. Firstly I define a couple of methods (This does assume that you have the classes already generated for each of the tables.)
public static DbSet GetDbSet(MyDbContext db, string tableName)
{
// Find the EF entity that corresponds to this table
ObjectContext objectContext = (db as IObjectContextAdapter).ObjectContext;
var mappings = GetEntityMappings(objectContext);
var entityName = mappings[tableName];
// Now get the corresponding DbSet
DbSet dbSet = (DbSet)db.Set(Type.GetType("schema_name." + entityName));
return dbSet;
}
public static Dictionary<string, string> GetEntityMappings(ObjectContext objectContext)
{
var EntityMappings = new Dictionary<string, string>();
// Build a list of database table names to EF entity names
// Get a list of entities
MetadataWorkspace workspace = objectContext.MetadataWorkspace;
var entities = workspace.GetItems<EntityType>(DataSpace.CSpace);
foreach (EntityType et in entities)
{
// Get the entity set that uses this entity type
var entitySet = workspace
.GetItems<EntityContainer>(DataSpace.CSpace)
.Single()
.EntitySets
.Single(s => s.ElementType.Name == et.Name);
// Find the mapping between conceptual and storage model for this entity set
var mapping = workspace.GetItems<EntityContainerMapping>(DataSpace.CSSpace)
.Single()
.EntitySetMappings
.Single(s => s.EntitySet == entitySet);
// Find the storage entity set (table) that the entity is mapped to
var table = mapping
.EntityTypeMappings.Single()
.Fragments.Single()
.StoreEntitySet;
string tableName = (string)table.MetadataProperties["Table"].Value ?? table.Name;
EntityMappings.Add(tableName, et.Name);
}
return EntityMappings;
}
Basically it is poking around in the EntityFramework metadata in order to map a string of a table name to the entity name (which aren't necessarily the same). This is slightly simplified as ideally you would cache the mappings.
Then finally call the GetDbSet() method, I've used AsQueryable() as I potentially need to add where clauses etc.
var dbSet = GetDbSet(db, "table_name").AsQueryable();
I have a simple web application which allows an user to upload 2 .csv-files containing certain data. To persist the data I use the Entity Framework in two different Import-methods.
First Import-method
public void ImportOne(string path)
{
StreamReader sr = new StreamReader(path);
using (var db = new ContextEv("RndContext"))
{
db.Database.ExecuteSqlCommand("DELETE FROM TableA");
db.Database.ExecuteSqlCommand("DELETE FROM TableB");
while (!sr.EndOfStream)
{
string[] data = sr.ReadLine().Split(';');
string houseId = data[0];
House house = new House()
{
HouseId = houseId,
};
House dummy = db.Houses.Find(houseId);
if (!dummy.HouseId.Equals(house.HouseId))
{
db.Houses.Add(house);
}
}
}
}
}
This line fails: House dummy = db.Houses.Find(houseId); with the following exception:
The type of one of the primary key values did not match the type
defined in the entity. See inner exception for
details.\r\nParametername: keyValues
ErrorContext of InnerException:
keyword 'AS', line 1, column 22
ErrorDescription of InnerException:
The query syntax is not valid.
Alright, I checked if really the type is the problem here. However I haven't found anything wrong.
The "funny" thing about it is, that I use the same Find-method in another Import-method and it works without any exception!
using (var db = new ContextEv("RndContext"))
{
db.Database.ExecuteSqlCommand("DELETE FROM TableC");
db.Database.ExecuteSqlCommand("DELETE FROM TableD");
StreamReader sr = new StreamReader(path);
while (!sr.EndOfStream)
{
string[] data = sr.ReadLine().Split(';');
string houseId = data[5];
House house = db.Houses.Find(houseId);
...
...
db.SaveChanges();
}
}
I wasn't sure which code is really needed for you to answer my question but I'd be very happy to post more if someone asks for a particular code.
UPDATE 1 ANSWER TO user89861
'db.Houses.ToList().Find(h => h.HouseId == houseId)' threw an
exception of type 'System.NullReferenceException'
" bei
System.Data.Entity.Internal.Linq.InternalQuery1.GetEnumerator()\r\n
bei System.Data.Entity.Internal.Linq.InternalSet1.GetEnumerator()\r\n
bei
System.Data.Entity.Infrastructure.DbQuery1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator()\r\n
bei System.Collections.Generic.List1..ctor(IEnumerable1
collection)\r\n bei
System.Linq.Enumerable.ToList[TSource](IEnumerable1 source)"
Find() returns null if result is not found, so you should validate the variable house before accesssing its members:
House house = db.Houses.Find(obj => {return obj.HouseId == houseId;});
if (house == null) continue; //go to next iteration
I managed to fix this weird bug. In the Context-Constructor I have simply added
public ContextEv(string dbName) : base("name=" + dbName)
{
//Database.SetInitializer(new DropCreateDatabaseAlways<ContextEv>());
}
However at first I had to delete every table manually because the code above didn't really do it. And after letting it run once I had to comment the code and run again so the table appear (why, I really do not know.. maybe some of you know).
Thank you everyone very much for your help! I really learned some things with your answers.
The .find() method is not strongly typed, it uses object parameters and hence the error you describe occur during runtime, if the data type of the parameters passed to it are incompatible (e.g. you pass a string where an integer value is expected).
Generally, I usually prefer to use .Where() and / or .FirstOrDefault() rather than .Find(), because you have to specify the field names, e.g.
var usr=db.Employees.Where(
x => x.FirstName=="James" && x.LastName=="Bond").FirstOrDefault();
or you could directly write:
var usr=db.Employees.FirstOrDefault(
x => x.FirstName=="James" && x.LastName=="Bond");
Both return Null if no records were found.
It makes the code more clear for later reviews and possibly required data model changes - consider if you have to add a field to a compound primary key, such as birth date in the example above: In this case it is easy to see that you have to add it to the .Where statement.
Because it is strongly-typed, it also allows you to use intellisense to look up the proper data type of the fields involved in the query by right-clicking and selecting "go to definition."
All these benefits make trouble-shooting much easier. Also, .Where and .FirstOrDefault are more versatile than .Find, because they support multiple kinds of sequences (look here at SO for a more detailed explanation) and are not restricted to primary keys.
The downside is that .Any(), .Single(), .First(), .Where() (and their ...OrDefault() pendents) are generating SQL queries and hence roundtrips to the database, in a similar way as if you would use ad-hoc queries or call stored procedures.
However, if you're not searching within the primary key, you have to use them. The tool LinqPad shows nicely how such queries are translated by EF into SQL code (assuming Northwind as example database):
SELECT TOP (1)
[Extent1].[EmployeeID] AS [EmployeeID], -- primary key (PK)
[Extent1].[LastName] AS [LastName], [Extent1].[FirstName] AS [FirstName],
[Extent1].[Title] AS [Title], [Extent1].[TitleOfCourtesy] AS [TitleOfCourtesy],
[Extent1].[BirthDate] AS [BirthDate], [Extent1].[HireDate] AS [HireDate],
[Extent1].[Address] AS [Address], [Extent1].[City] AS [City],
[Extent1].[Region] AS [Region],
[Extent1].[PostalCode] AS [PostalCode], [Extent1].[Country] AS [Country],
[Extent1].[HomePhone] AS [HomePhone], [Extent1].[Extension] AS [Extension],
[Extent1].[Photo] AS [Photo], [Extent1].[Notes] AS [Notes],
[Extent1].[ReportsTo] AS [ReportsTo], [Extent1].[PhotoPath] AS [PhotoPath]
FROM [dbo].[Employees] AS [Extent1]
WHERE (N'James' = [Extent1].[FirstName]) AND (N'Bond' = [Extent1].[LastName])
Here you can see that it makes sense to restrict the fields in the resultset wherever possible, otherwise the generated query will return values you're not interested in and return an unnecessarily high amount of data.
The .Find() method will only work here with the EmployeeID, since that is the primary key (PK). For all other fields involved in queries, you can't use .Find() and have to use the other query methods (.Where(), .Single(), .First() or .Any()).
In your particular case it would look like (note you should only create a new object if required, so I have moved that to the if statement):
string houseId = data[0];
House dummy = db.Houses.FirstOrDefault(x=>x.HouseId==houseId);
if (dummy==null)
{
House house = new House()
{
HouseId = houseId
};
db.Houses.Add(house);
}
But note in this case, it can be further optimized by using .Any():
string houseId = data[0];
if (!db.Houses.Any(x => x.HouseId == houseId))
{
House house = new House()
{
HouseId = houseId,
};
db.Houses.Add(house);
}
if you don't need the to retrieve the object from database anyway, which avoids returning unnecessary data (as mentioned before).
So what I was trying was to fetch only those columns from table which has to be updated and I tried it as below:
var user = (from u in context.tbl_user where u.e_Id == model.Id select
new { u.first_name, u.last_name, u.role, u.email, u.account_locked })
.FirstOrDefault();
But when I tried to assign new value to the fetched data as below
user.first_name = model.FirstName;
I saw below error getting displayed
Property or indexer 'anonymous type: string first_name, string
last_name, string role, string email, bool account_locked.first_name'
cannot be assigned to -- it is read only
But when I retrieved all the values from table without filtering as below it worked fine.
var user = (from u in context.tbl_user where u.e_Id == model.Id select u).FirstOrDefault();
Why it doesn't work for first query. I've read in many sites that it is good to retrieve only required properties from database in terms of performance and security. But I am really not able to understand what's wrong with the first approach I opted. Any explanations are much appreciated.
Update
Are there any other ways to fetch only required column and update them and store them back?
Anonymous Types properties are read-only so you can not change them.
Stop doing micro-optimizing or premature-optimization on your code. Try to write code that performs correctly, then if you face a performance problem later then profile your application and see where is the problem. If you have a piece of code which have performance problem due to finding the shortest and longest string then start to optimize this part.
We should forget about small efficiencies, say about 97% of the time:
premature optimization is the root of all evil. Yet we should not pass
up our opportunities in that critical 3% - Donald Knuth
If you want to just fetch specific columns you can create a custom class and fill the properties in your query like others have mentioned.
As others said anonymous type is read only, for achieving what you want, you will have to create a type for it with properties that are required:
public class User
{
public string FirstName {get;set;}
public string LastName {get;set;}
.....................
.....................
}
and then, you have to use it in your linq query:
var user = (from u in context.tbl_user
where u.e_Id == model.Id
select new User
{
FirstName = u.first_name,
LastName = u.last_name,
......................,
.....................
}).FirstOrDefault();
Anonymous types are read-only by design.
In order to retrieve something that you can edit, you have to create a class and select your entities into that class, as done here: How to use LINQ to select into an object?
Or here:
var user = (from u in context.tbl_user where u.e_Id == model.Id select
new User_Mini { u.first_name, u.last_name, u.role, u.email, u.account_locked })
.FirstOrDefault();
Note: you won't be able to call context.SubmitChnages() when editing this new object. You could do something like this though: LINQ to SQL: how to update the only field without retrieving whole entity
This will allow you to update only certain parts of the object.
I've been learning C# / LINQ / ASP.NET / MVC 3 / EF for a few months now comming from Java / Icefaces / Ibatis base (Real world uses .NET D;). I really enjoy LINQ / Entity Framework from the .NET Framework but I'm having a few issues understand what's really happening behind the scenes.
Here's my problem:
I'm using a AJAX / JSON fed jQuery datatable (that I highly recommend to anyone in need of a free web datatable system by the way). I have a method in my MVC3 application that returns a JSON result of the data needed by the table, doing the sorting and all. Everything is working nicely and smoothly. However, I'm having a concern with the "dirty" hack I had to do to make this work.
Here's the complete code:
//inEntities is the Entity Framework Database Context
//It includes the following entities:
// Poincon
// Horaire
// HoraireDetail
//Poincon, Horaire and HoraireDetail are "decorated" using the Metadata technic which
//adds properties methods and such to the Entity (Like getEmploye which you will see in
//the following snippet)
//
//The Entity Employe is not a database data and therefor not handled by the EF.
//Instead, it is a simple object with properties that applies Lazy Loading to get an
//Employe Name based off of his Employe ID in the Active Directory. An employe object
//can be constructed with his Employe ID which will expose the possibility of getting
//the Employe Name from the AD if needed.
[HttpPost]
public JsonResult List(FormCollection form)
{
String sEcho;
int iDisplayStart;
int iDisplayLength;
String sSearch;
int iSortingCols;
Dictionary<String, String> sorting;
try
{
sEcho = form["sEcho"];
iDisplayStart = int.Parse(form["iDisplayStart"]);
iDisplayLength = int.Parse(form["iDisplayLength"]);
sSearch = form["sSearch"];
iSortingCols = int.Parse(form["iSortingCols"]);
sorting = new Dictionary<string,string>();
for (int i = 0; i < iSortingCols; i++)
sorting.Add(form["mDataProp_" + form["iSortCol_" + i]].ToUpper(), form["sSortDir_" + i].ToUpper());
}
catch
{
HttpContext.Response.StatusCode = 500;
return null;
}
var qPoincon = inEntities.Poincons.AsEnumerable();
var lPoincon = qPoincon.Select(o => new
{
o.id,
emp = o.getEmploye(),
o.poinconStart,
o.poinconEnd,
o.commentaire,
o.codeExceptions
}).AsEnumerable();
//Search
lPoincon = lPoincon.Where(p => (p.emp.empNoStr.Contains(sSearch) || p.emp.empNom.Contains(sSearch) || (p.commentaire != null && p.commentaire.Contains(sSearch))));
//Keep count
int iTotalDisplayRecords = lPoincon.Count();
//Sorting
foreach(KeyValuePair<String,String> col in sorting)
{
switch (col.Key)
{
case "EMPNO":
if (col.Value == "ASC")
lPoincon = lPoincon.OrderBy(h => h.emp.empNo);
else
lPoincon = lPoincon.OrderByDescending(h => h.emp.empNo);
break;
case "POINCONSTART":
if (col.Value == "ASC")
lPoincon = lPoincon.OrderBy(h => h.poinconStart);
else
lPoincon = lPoincon.OrderByDescending(h => h.poinconStart);
break;
case "POINCONEND":
if (col.Value == "ASC")
lPoincon = lPoincon.OrderBy(h => h.poinconEnd);
else
lPoincon = lPoincon.OrderByDescending(h => h.poinconEnd);
break;
case "COMMENTAIRE":
if (col.Value == "ASC")
lPoincon = lPoincon.OrderBy(h => h.commentaire);
else
lPoincon = lPoincon.OrderByDescending(h => h.commentaire);
break;
}
}
//Paging
lPoincon = lPoincon.Skip(iDisplayStart).Take(iDisplayLength);
//Building Response
var jdt = new
{
iTotalDisplayRecords = iTotalDisplayRecords,
iTotalRecords = inEntities.Poincons.Count(),
sEcho = sEcho,
aaData = lPoincon
};
return Json(jdt);
}
As you can see, when I'm grabbing the entire list of "Poincons" from the EF and turning it into a Enumerable. From my current understanding, turning the LINQ query into a Enumerable "kills" the link to the EF, or in other words, will generate the SQL required to get that list at that point instead of keeping the LINQ data until the end and execute a percise query that will return only the data you require. After turning this LINQ Query into a Enumerable, I'm heavily filtering the LINQ (since there is paging, sorting, searching in the datatable). This leads me to thinkg that what my code is currently doing is "Grab all the "Poincons" from the database and put it into the web server's memory as a Enumerable, do your work with the Enumerable then serialize the result as a JSON string and send it to the client.
If I'm correct, the performance hit is quite heavy when you hit the couple thousand of entries (which will happen quite fast once in production... everytime an employe comes to work, it will add 1 entry. 100 employes, ~300 work days a year, you get the idea).
The reason for this hack is that the EF does not know what "getEmploye" method of "Poincon" is, therefor throwing an exception at runtime similar to this:
LINQ to Entities ne reconnaît pas la méthode « PortailNorclair.Models.Employe getEmploye() », et cette dernière ne peut pas être traduite en expression de magasin.
Approximated traduction (If anyone can let me know in a comment how to configure IIS / ASP.NET to display errors in english while keeping the globalization in a foreign language, I would be really grateful. French information about error messages is sometimes lacking):
LINQ to Entity does not recognize the method " PortailNorclair.Models.Employe getEmploye()" and the following could not be translated to a SQL expression.
The "getEmploye" method instances and returns a Employe object with the employe id found in the Poincon object. That Employe object has properties that "lazy loads" information like the employe name from the Active Directory.
So the question is: How can I avoid the performance hit from using .AsEnumerable() on the non-filtered list of objects?
Thanks a lot!
The "getEmploye" method instances and returns a Employe object with
the employe id found in the Poincon object. That Employe object has
properties that "lazy loads" information like the employe name from
the Active Directory.
You should be storing the Employee Name in the database, so you can then order, sort, skip and take in your Linq Query without having to load every employee object.
If empNoStr, empNom, and empNo were all in the database, you could retrieve just the records you want, and call getEmploye() (loading whatever else you need from active directory, or wherever) for each of those.
There are some classes on which your program performs its main work.
There are other classes which represent to database rows.
If you keep them separated, you can also separate actions you intend to occur in the database from actions you intend to perform locally. This makes it trivial to avoid loading the full table, when specific rows are required.
I see you're also doing Paging locally, while the database can do that and save your webserver some memory.
Hi i am trying to get to grips with Dapper.
My situation is i want to pull two values from a query into two separate strings. Im not sure if i am going about this in the correct way, but this is what i am doing:
string sql = #"Select type, name
FROM ZipData
WHERE Zip = #zip";
using (var multi = conn.QueryMultiple(sql, new { zip = zip }))
{
string result = multi.Read<string>().SingleOrDefault();
}
And i am getting Cannot access a disposed object. Object name: 'GridReader'. when trying to read the second string.The thing is it gets the first value correctly and has both the fields in in the reader i am trying to get. Im sure im misusing the api.
What am i doing wrong here? Ive googled but can find a specific example.
You are mis-using QueryMultiple. That is defined for compound SQL statements that return multiple result sets. Something like:
SELECT Foo FROM MyTable;
SELECT Bar FROM MyOtherTable;
On the other hand, you are trying to get two different columns from a single result set, so you should just use the normal Query method:
var result = conn.Query(sql, new { zip = zip }).Single();
var type = result.type;
var name = result.name;
Query returns an enumerable (because generally a query can return multiple rows). It appears that you only want one row, however, so we invoke .Single at the end to just get that row. From there, the return type is dynamic so you can simply refer to the properies implied by the columns in your SELECT statement: type and name.