Exception for disposed DataContext even with ToList() applied - c#

I'm in the classic old situation - trying to return a set of elements that I query via Linq to SQL, but that are dependent on the DataContext in which they were queried.
Pretty much every example I've seen just adds ToList(), which I've done, but it didn't solve it. I thought the ToList() call would force evaluation of the query, but it doesn't seem to.
[WebMethod]
public List<WebService.Score> GetTopNScores(string gameName, int count)
{
using (var dc = new WebService.MyDataContext())
{
var q = from row in dc.GetTable<WebService.Score>()
where row.GameName == gameName
orderby row.ScoreValue descending
select row;
return q.Take(count).ToList();
}
}
I'd like to avoid DataLoadOptions and having to specify the dependency for each column, because the database format is still evolving and I'd need to update the code for every change.
Any idea why the ToList() call is not forcing the evaluation of the data? And what solution I can attempt?

Related

How could I get a Relative Entity's count using EF6

I have a Entity called "Client", and another Entity called "Card".
A Client may have many Cards.
My Client Entity looks like this:
public class Client{
public virtual ICollection<Card> Cards {get; set;}
}
Now I want to show the Client data in a DataGrid in WPF, and I want to get Cards Count data,so I add a property to Client Entity, which like this:
public class Client{
public virtual ICollection<Card> Cards {get; set;}
public int CardCount
{
return Cards.Count;
}
}
And then I query the data with Linq and Bind to view
var query = from n in db.Clients select n;
When I run the Application, I got a Exception just right on the return Cards.Count; line;
System.ObjectDisposedException
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
So how could I correctly get the cards count?
There is a way simpler method than the other answers here show. Please also realize that solutions such as
var client = db.Clients.FirstOrDefault(c=> c.Id = someid); //get a client
if (client != null)
{
cardCount = client.Cards.Count;
}
will cause an issue called Select N+1 problem. Read up on it if interested, but in a nutshell, it means the following:
Because you are not only interested in one exact client, but you want to display N clients, you need to do one (1) query to get just the clients. Then, by doing the FirstOrDefault stuff, you are actually doing one (1) extra db roundtrip to the database per Client record, which results in an additional N * 1 = N roundtrips. What this means that, if you were to just query the Clients without any related data, you could get however many client records you like, in just one query. But by fetching related data to each of them one-by-one, you are doing way too many db roundtrips.
Here is a way to solve this issue, by using joins and projections. You can get all the data you need in a single DB access:
using (var context = GetDbContext())
{
return context.Clients.Select(cli => new YourViewModel
{
Name = cli.FullName,
// Other prop setters go here
CardCount = cli.Cards.Count
}).Skip((page - 1) * pageSize).Take(pageSize).ToList();
}
You might be wondering, what's the difference afterall? Well, here, you are not working with materialized objects, as others call them here, but with a DbContext. By applying the proper LINQ operators to it (note, that this works not just with DbContext, but also with any IQueryable (well obviously not if you call AsQueryable() on an already in-memory collection but whatever)), LINQ to Entities can construct a proper SQL to join the tables and project the results and therefore you fetch all required data in one go. Note that LINQ to Entities IS ABLE to translate the cli.Cards.Count into a proper SQL Count statement.
You can get the count without loading the entities like this:
using (var context = new MyContext())
{
var client = context.Client.Find(clientId);
// Count how many cards the client has
var cardsCount = context.Entry(client)
.Collection(b => b.Cards)
.Query()
.Count();
}
More information on MSDN page.
You get an ObjectDisposedException if you do not materialize the retreived query. In the following case, the query gets executed only when you Access the first time the list from GetNonMaterialized and not before leaving the method. Fact of this the db is disposed because of lost of scope.
public IEnumerable<Client> GetNonMaterialized()
{
return from n in db.Clients select n;
}
In the following case the query is executed before leaving the method.
public IEnumerable<Client> GetMaterialized()
{
return (from n in db.Clients select n).ToList();
}
Always be sure that the query is executed before exiting the scope of a ObjectContext.
If you want to know whether the query is executed and when enalbe Logging of EF.
How can I turn off Entity Framework 6.1 logging?

NpGsql EntityFramework 6 - "An operation is already in progress"

I am working on a project to connect to PostgreSQL database using NpGsql EntityFramework 6. I am getting the exception in question heading, when I try to execute the query in GetAdminUsersCount:
public class GenieRepository : IDisposable
{
GenieDbContext db = new GenieDbContext();
public IEnumerable<User> GetUsers()
{
return db.Users;
}
}
public int GetAdminUsersCount()
{
return repo.GetUsers().Where(u => u.Role.RoleName == "Administrator").Count();
}
What is the reason for this error and how to resolve it?
Use a ToList<T> right after the LINQ query like this:
using (ElisContext db = new ElisContext()) {
var q = from a in db.aktie select a;
List<aktie> akties = q.ToList<aktie>();
foreach (aktie a in akties) {
Console.WriteLine("aktie: id {0}, name {1}, market name {2}"
, a.id, a.name, a.marked.name);
}
}
Note the q.ToList<T> which does the trick. .NET delays the execution of the linq statement to the latest moment, which may be part of the problem. I have tried to use q in the foreach without success.
The issue is caused by the return type of the GetUsers() method. Since it is IEnumerable<User>, LINQ-to-SQL will load the result into memory (row by row) and subsequent Where, OrderBy, Select, Count, etc. calls will be executed in memory. When the Where condition is evaluated, the original result set is still being iterated over, but the relation User.Role needs to be resolved on the DB (that's where the "An operation is already in progess" error message comes from).
If the return type is changed to IQueryable<User>, the query won't be executed until the Count method is called, and furthermore, the whole query will be translated into SQL returning only the count without ever loading any User records into memory.
See also this related question/answer: Returning IEnumerable<T> vs. IQueryable<T>
The answer suggesting to call ToList() on the query, will load the entire result set into memory, which makes your error go away, but depending on the size of the result set, could also be very inefficient.

ASP.NET MVC 3 / Razor - Strange Caching Issue

I'm using a LinqToSql query to select a list of groups from a database and spew out a table. I've written a custom class to cache the results of this query for better performance. Trouble is, whenever I implement the caching class, I get weird appending behaviour from the outputting statement.
My results are outputted in the format
Test Group (1)
where "Test Group" is the name, and (1) is the number of members within that group. Here's the code that appends the count to the name (from the view)
<td>#group.group_name (#group.num_total)</td>
When I pull this from a live linq query returning groups, everything works as expected.
However, when I use my caching class, every successive page load adds the number on to the end of the group title:
Test Group (1) (1) (1) (1) (1) (1)
This only happens when I use the caching class (included below). I've been over the cache class and there's no reason I can see why this would be happening.
I can think of several workarounds for this issue, so its not a show stopper, but I'm curious as to what the fudge is going on. Any ideas?
Caching Class:
public class Cache
{
public static int user_id {
get { return
Convert.ToInt32(
Membership.GetUser(
HttpContext.Current.User.Identity.Name
).ProviderUserKey
);
}
}
public static void GetGroups_InvalidateCache()
{
if (HttpContext.Current.Cache["GetGroups_" + user_id] != null)
HttpContext.Current.Cache.Remove("GetGroups_" + user_id);
}
public static ICollection<Groups> GetGroups()
{
if (HttpContext.Current.Cache["GetGroups_" + user_id] == null)
{
using(DBContext db = new DBContext())
{
var Groups = (from g in db.Groups
where g.user_id == user_id
select g).ToList();
HttpContext.Current.Cache.Insert(
"GetGroups_" + user_id,
Groups,
null,
DateTime.Now.AddMinutes(5),
TimeSpan.Zero
);
}
}
return HttpContext.Current.Cache["GetGroups_" + user_id]
as ICollection<Groups>;
}
}
UPDATE:
I've now implemented Adam Tuliper and Paul Tyng's suggestions of calling the data context with the using clause, ending the linq statement with ToList() and using ICollection instead of IQueryable. The problem is still occurring.
Another interesting observation: The issue only happens if I navigate away to another page and return. If I simply refresh the page, it doesn't happen (Although any previous number additions still remain when I refresh)
Instead of returning IQueryable, try using simply IEnumerable and also using
using(DBContext db = new DBContext())
{
var Groups =
(from g in db.Groups
where g.user_id == user_id
select g).ToList();
...
}
Also dispose of your context as in the above statement (with the using clause)
The ToList() forces the execution "now" - I think you are potentially having deferred execution issues

Castle ActiveRecord - 2nd level cache - need explanation of this behaviour

I am just doing some experiments on Castle AR and 2nd level cache of NH. In the following two methods, I can see caching working fine but only for the repetition of the call of each. In other words if I call RetrieveByPrimaryKey twice for same PK, the object is found in cache. And if I call RetrieveAll twice, I see SQL issued only once.
But if I call RetrieveAll and then RetrieveByPrimaryKey with some PK, I see two SQL statements getting issued. My question is, Why AR does not look for that entity in cache first? Sure it would have found it there as a result of previous call to RetrieveAll.
public static T RetrieveByPrimaryKey(Guid id)
{
var res = default(T);
var findCriteria = DetachedCriteria.For<T>().SetCacheable(true);
var eqExpression = NHibernate.Criterion.Expression.Eq("Id", id);
findCriteria.Add(eqExpression);
var items = FindAll(findCriteria);
if (items != null && items.Length > 0)
res = items[0];
return res;
}
public static T[] RetrieveAll()
{
var findCriteria = DetachedCriteria.For<T>().SetCacheable(true);
var res = FindAll(findCriteria);
return res;
}
You're using caching on specific queries. that means that cache lookup is done in the following way:
search the cahce for results of a query with identical syntax AND the same parameters. If found- use cached results.
nHibernate (this has nothing to do with AR, by the way) doesn't know that logically, one query 'contains' the other. so this is why you're getting 2 db trips.
I would suggest using ISession.Get to retreive items by ID (it's the recommended method). I think (not tested it though) that Get can use items cached by other queries.
here's a nice blog post from ayende about it.

Sequence contains no elements Linq-to-Sql

I'm having very strange problem in this simple linq query
return (from doc in db.umDocumentActions
where doc.sysDocument.ModuleID == modid
join roleaction in db.umRoleActions on doc.DocumentActionID equals roleaction.DocumentActionID
where roleaction.RoleID == roleID
select new { doc.DocumentID, roleaction.RoleID }).Count() > 0;
When this query is called it gives invalid operation exception telling me that sequence contains no elements. It happens when there is fair amount of traffic on the site. I am using following static method to get instance of datacontext.
public static EvoletDataContext Get()
{
var connection = ProfiledDbConnection.Get(new SqlConnection(ConfigurationManager.ConnectionStrings["cnstring"].ToString()));
return new EvoletDataContext(connection);
//return DataContextUtils.CreateDataContext<EvoletDataContext>(connection);
}
I'm afraid that this method is creating problem as static methods are not thread safe. Any views?
My best guess would be that sysDocument is actually a seperate table with a reference to DocumentID. This would normally mean that there would be a related collection of sysDocuments in the document class, but I'm guessing that you've changed the cardinality to "one to one" in the Linq to SQL designer.
When using a "one to one" cardinality, Linq to SQL uses the Single() method behind the scenes to get the sysDocument. This means that if there are no related sysDocuments you will get an invalidoperation exception.
You can fix this by wither changing the cardinality in you Linq model from "one to one" to "one to many" and use the SingleOrDefault() method to get the related sysDocument from the sysDocuments collection.
If that doesn't sound appealing you can look in the database to find which document doesn't have a related sysDocument and fix it manually.
UPDATE:
Instead of basing the query off the documentActions, try basing it off the sysDocument table instead. I've had to guess at what the table will be called so this might not compile, but hopefully you get the idea:
var query = from sysDocument in db.sysDocuments
where sysDocument.ModuleID == modid
let doc = sysDocument.umDocumentAction
join roleaction in db.umRoleActions on doc.DocumentActionID equals roleaction.DocumentActionID
where roleaction.RoleID == roleID
select new { doc.DocumentID, roleaction.RoleID };
//return true if there are any results (this is more efficient than Count() > 0)
return query.Any();

Categories

Resources