I have found many other posts but they are nt facing exactly the same problem. And they are using a slightly different code. SO I think it is worth reviewing this.
I´m using EF6 code first, and I created a Client Entity that has some navigation properties.
I´ll post just the relevant code, consider there are a few more properties and foreign keys as well, but not relevant to the problem. Model is generating ok.
public class Client
{
public Client()
{
JobsExperiences = new Collection<JobsExperience>();
CapacitationCourses = new Collection<CapacitationCourse>();
ScholarLevelDetails = new Collection<ScholarLevelDetail>();
Relatives = new Collection<Relative>();
}
public long ClientID { get; set; }
public virtual ICollection<ScholarLevelDetail> ScholarLevelDetails { get; set; }
public virtual ICollection<JobsExperience> JobsExperiences { get; set; }
}
Now I created a ClientServices class where I put all methods that get or send data from and to the data base., ther I have this code, which is working randomly, I´ll try to explain clearly.
internal Client GetClient(string userId, bool lazyLoadingEnabled = true)
{
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled=lazyLoadingEnabled;
var client = (from _client in context.Client
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
return client;
}
}
My objective some cases is to retrieve just the client attributes, and sometimes all attributes including navigation properties.
In my controller I have a line like this
var client = uuc.GetClient(user.Id, false);
or this
var client = uuc.GetClient(user.Id);
When I run the first sentence, the navigation properties are initialized but all has Count=0, even when my DB has records associated. I think, if lazy loading is disabled, it means eager loading is enabled, but it seems not. However, there is no Load() Method in the navigation properties to force load.
When I run the second sentence, the navigation properties throws an exception 'client.ScholarLevelDetails' threw an exception of type 'System.ObjectDisposedException'. This is thrown one line after the sentence, loking at the navigation properties in the watch. However, and this is the weirdest part, if I step back to the sentence and debug stepping into the method, All navigation properties are loaded correctly.
Why the code behaves differently if running at once than running stepping into the method?
I presume the using statement scope finishes before that the navigation properties load, but why disabling lay loading doe snot retrieve them either?
How can I code this to have a consistent behaviour?
I change the query code Ihad in Linq with this one.
internal Client GetClient(string userId, bool lazyLoadingEnabled = true)
{
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled = lazyLoadingEnabled;
var client = context
.Client
.Include(s => s.ScholarLevelDetails)
.Include(s => s.JobsExperiences)
.Include(s => s.CapacitationCourses)
.Include(s => s.Relatives)
.FirstOrDefault(s => s.ApplicationUserId == userId);
return client;
}
}
And now it works. however I still have some questions I´d lve to discuss with you readers and colleagues.
Why plain Linq doesn´t work?
Why it doesn matter if lazyloading is enabled or not, this code works the same everytime?
The problem is that your context fell out of scope before the navigational properties could be loaded.
To do what you want, you would need to change how you are working with your context, or just eager load the entities you are going to need via table join(s) using the query syntax that you are using, or via .Include() lambda expressions if you use the lambda query syntax.
using (var context = new ApplicationDbContext())
{
context.Configuration.LazyLoadingEnabled=lazyLoadingEnabled;
var client = (from _client in context.Client
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
return client; //at this point, your context is gone, and no
//navigational properties will be loaded onto your client object,
//even if you try to navigate them. You may even get exceptions if
//attempting to navigate to some properties.
}
Here is a join example:
var client = (from _client in context.Client
join t in context.Table on _client.Val equals t.val //this will eager load Table property on client.
where _client.ApplicationUserId == userId
select _client).FirstOrDefault();
You should use Include method for properties.
_context.Client.Include(c=>c.JobsExperiences) ... and all props like this
but for you is better not to use lazy loading.
Cause context become inactive after you return from method.
Using EF 6, I had to use (lamba's and Client keyword is unavailable):
using (var context = new SyntheticData.EF.DBContext())
{
var item = (from t in context.TEMPLATEs
.Include("DATASET")
.Include("COLUMNs")
.Include("SORTs")
.Include("FILTERs")
where t.USERID == identityname && t.ID == id select t).FirstOrDefault();
return item;
}
This filled in relationship classes with syntax like:
item.DATASET.xxx
item.COLUMNs[0].xxx
The "using (var context" construct is good practice because it insures your connection to the database will be released back to the pool. If you don't, you can end up running out of conenctions for busy systems.
Just in case this helps someone I was not getting any navigation properties and my problem seemed to be that EF did not properly hookup the properties because I was using an interface as the navigational property type and I needed to use actual type. Unless anyone knows how to use annotations or something to tell EF the actual type that the property is mapped to.
Related
I have landed up in a situation here.
I am loading the User from a custom function here :
public User GetUserById(int Id)
{
var User = DBContext.Users.SingleOrDefault(x=>x.Id == Id);
return User;
}
The problem is in the controller where I call this!
var User = GetUserById(Id);
User.Language = UserModel.Language;
//UpdateModel(User); //I tried this but not working.
DBContext.SaveChanges();
The database is not being updated.
I tried loading the User in the controller directly with linq and things go fine.
But, Why is it not working the other way?
Is there any workaround to make it work with this function?
Isn't there any function like DBContext.Users.Update(User). I remember using something similar, but am not able to recollect!
As EBrown said, they need to be the same instance of DbContext. I'm betting your service uses one context and the controller another. So now when it gets to the controller it is disconnected and not tracked by EF.
The solution is to reconnect it to the context used by the controller (there are other ways to achieve this as well).
var User = GetUserById(Id); // returns a disconnected user
User.Language = UserModel.Language;
// Attach back to context and tell EF it is updated
DBContext.Users.Attach(User);
DBContext.Entity(User).State=EntityState.Modified;
DBContext.SaveChanges();
If this is your postback code, you could just as well write aUserUpdate function:
public void UpdateUser(UserModel userViewModel)
{
var userEntity = DBContext.Users.Find(userViewModel.Id); // Get your user from database
Mapper.Map(userViewModel, userEntity); // Use Automapper to move the changed fields into your entity
DbContext.SaveChanges();
}
Then your controller POST is simply:
if (ModelState.IsValid)
{
UpdateUser(UserModel);
// redirect to list or where ever...
}
My project is C# .NET, MVC 5, EF6. I'm getting an ObjectDisposedException on using an object in a view that has been gotten from the database. I've read probably EVERY similar question, but .Include() does NOT work; I'm thinking the problem doesn't have anything to do with lazy loading.
The controller method:
public ActionResult Browse()
{
List<QuestionGroup> questionGroupsWithLinks = new List<QuestionGroup>();
using (CLASSContext context = new CLASSContext())
{
questionGroupsWithLinks = context.QuestionGroup.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128).ToList();
return View("Browse", questionGroupsWithLinks);
}
}
I've tried having the using statement not wrap around the view, I've tried declaring questionGroupWithLinks in different places, I've tried iterating through questionGroupWithLinks and assigning one of its properties in hopes that that would load it (didn't make any difference, because the problem is only in the view. It's always loaded as long as you're in the controller method), and I've tried other things as well.
The view (simplified):
#model List<CLASSOnlineAssessments.Models.Assessments.QuestionGroup>
<div class="page-copy">
#if (Model != null)
{
foreach (QuestionGroup qg in Model)
{
//More code here; but it always fails before this point.
}
}
</div>
I've tried using Model.First() to access a question group instead of foreach, but that doesn't make any difference.
Let me know if I can clarify anything or post more information.
Have you tried this?
public ActionResult Browse()
{
CLASSContext context = new CLASSContext();
List<QuestionGroup> questionGroupsWithLinks = context.QuestionGroup
.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128).ToList();
return View("Browse", questionGroupsWithLinks);
}
If this does not cause the error, then it does smell like an issue with lazy loading, and you should post your source for the QuestionGroup entity as well as the Question and Answer entities so that we can provide more help.
In all likelihood you have some other virtual navigation or collection property on QuestionGroup, Question, or Answer that EF is trying to load while rendering your view. However since you disposed of the context, EF cannot lazy load it, and throws the exception.
BTW, don't use the above in production. You should always have the DbContext disposed of at the end of the request somehow. In reality, you should be doing something more like this:
public ActionResult Browse()
{
using (CLASSContext context = new CLASSContext())
{
List<QuestionGroupViewModel> questionGroupsWithLinks = context.QuestionGroup
.Include(qg => qg.Questions.Select(q => q.Answers))
.Where(qg => qg.QuestionGroupID == 128)
.Select(x => new QuestionGroupViewModel
{
Id = x.Id,
// ...etc.
})
.ToList();
return View("Browse", questionGroupsWithLinks);
}
}
With the above, you completely transfer all of the data from the entity attached to the context to a ViewModel Data Transfer Object. If the ObjectDisposedException error really is coming from EF, then the above would ensure that nothing further would happen with the DbContext after it gets disposed.
Let's assume that the below method lives in a WCF service. The UI retrieved an instance of the Status object, and makes a subsequent call to the service using this method. Instead of assigning the status to the user as I would expect, it attempts to insert the status. What am I doing wrong?
void Method(Status status)
{
//not sure if this is even needed, the status never changed
context.Statuses.ApplyChanges(status);
//get the first user from the database
User user = context.Users.Where(u => u.Id = 1).First();
//set user status to some existing status
user.Status = status;
//this throws an exception due to EF trying to insert a new entry
//into the status table, rather than updating the user.StatusId column.
context.SaveChanges();
}
The problem is that you are working with attached user. When the STE is attached to the context it behaves exactly in the same way as any other entity. More over its self tracking mechanism is not activated. So you must attach the status to the context before you set it to the user or it will be tracked as a new entity which has to be inserted:
void Method(Status status)
{
User user = context.Users.Where(u => u.Id = 1).First();
context.Attach(status);
user.Status = status;
context.SaveChanges();
}
Try this instead:
using (Entities ctx = new Entities())
{
ctx.Statuses.Attach(status);
ObjectStateEntry entry = ctx.ObjectStateManager.GetObjectStateEntry(status);
entry.ChangeState(EntityState.Modified);
//get the first user from the database
User user = ctx.Users.Where(u => u.Id = 1);
//set user status to some existing status
user.StatusID = status.StatusID;
ctx.SaveChanges();
}
Here's a tutorial on CRUD with Entity Framework if you're interested.
Have to write an answer because I can't yet comment on another answer (rep score < 50) [something weirdly not right about that, but I get why it's like that] because I wanted to add some clarity to #Ladislav's answer.
The Status object coming in from the WCF call did not come from the same context you are using to find the User object, so the tracking code is not fixed up with that context. This is why attaching it will allow you to save the assignment without the context thinking that status is a new entity requiring an insert into the database.
Using ASP.NET MVC and Entity Framework, I encounter some attach/detach errors when I need it to write changes to the database.
First here's how I get the records from my repository:
public PublishedApplication GetPublishedApplication(int id)
{
return dal.ExecuteFirstOrDefault(
context.PublishedApplication.Include("Customers")
.Where(w => w.Id == id));
}
Note that this is not being detached, and it has MergeOption = AppendOnly.
Another place, I call a method in my repo to attach a customer to the application:
public void AttachCustomer(int applicationId, int customerId)
{
var app = new PublishedApplication{ Id = applicationId};
var customer = new Customer { Id = customerId};
try
{
context.AttachTo("PublishedApplication", app);
context.AttachTo("Customer ", customer );
app.Customers.Add(customer);
context.SaveChanges();
}
catch
{
throw;
}
}
This, of course, gives me an error that the objects (app and customer) are already attached to an object context.
So I need to detach my objects in my repository. This is also more correct as I don't need to throw "query related information" around the web. But how can I do this? If I detach the entity in my GetPublishedApplication method, my graph of related data is lost, and I can't afford that. I need that graph elsewhere on my site. Can't use merge option NoTracking either.
So what options do I have?
Thanks in advance!
I think, there isn't easy solution for reattaching entities.
May be this post will help you:
Reattaching Entity Graphs with the Entity Framework on codeproject.com
In the following code doesn't work as
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
This doens't work, I get error msg. "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported."
How do you work with DataContexts throughout an application so you don't need to pass around a reference?
What
They really mean it with 'This is not supported.'. Attaching to an object fetched from another data context is not implemented.
There are a number of workarounds to the problem, the recommended way is by serializing objects, however this is not easy nor a clean approach.
The most simple approach I found is to use a readonly DataContext for fetching objects like this:
MyDataContext dataContext = new MyDataContext()
{
DeferredLoadingEnabled = false,
ObjectTrackingEnabled = false
};
The objects obtained from this context can be attached to another context but only applies to some scenarios.
The PLINQO framework generates detach for all entities making it easy to detach and reattach objects without receiving that error.
public void Foo()
{
CompanyDataContext db = new CompanyDataContext();
Client client = (select c from db.Clients ....).Single();
// makes it possible to call detach here
client.Detach();
Bar(client);
}
public void Bar(Client client)
{
CompanyDataContext db = new CompanyDataContext();
db.Client.Attach(client);
client.SomeValue = "foo";
db.SubmitChanges();
}
Here is the article that describing how the detach was implemented.
http://www.codeproject.com/KB/linq/linq-to-sql-detach.aspx
Yep. That's how it works.
You have tagged this asp.net so I guess it's a web app. Maybe you want one datacontext per request?
http://blogs.vertigo.com/personal/keithc/Blog/archive/2007/06/28/linq-to-sql-and-the-quote-request-scoped-datacontext-quote-pattern.aspx
(P.S. It's a lot harder in WinForms!)
I've created data access classes that encapsulate all the communication with Linq2Sql.
These classes have their own datacontext that they use on their objects.
public class ClientDataLogic
{
private DataContext _db = new DataContext();
public Client GetClient(int id)
{
return _db.Clients.SingleOrDefault(c => c.Id == id);
}
public void SaveClient(Client c)
{
if (ChangeSetOnlyIncludesClient(c))
_db.SubmitChanges();
}
}
Ofcourse you will need to keep this object instantiated as long as you need the objects.
Checking if only the rigth object has been changed is altso somewhat bothersom, you could make methods like
void ChangeClientValue(int clientId, int value);
but that can become a lot of code.
Attaching and detaching is a somewhat missing feature from Linq2Sql, if you need to use that a lot, you sould probably use Linq2Entities.
I took a look at this and found that it appears to work fine as long as the original DataContext has been disposed.
Try wrapping the DataContext with using() and make sure your changes occur after you've attached to the second DataContext? It worked for me..
public static void CreateEntity()
{
User user = null;
using (DataClassesDataContext dc = new DataClassesDataContext())
{
user = (from u in dc.Users
select u).FirstOrDefault();
}
UpdateObject(user);
}
public static void UpdateObject(User user)
{
using (DataClassesDataContext dc = new DataClassesDataContext())
{
dc.Users.Attach(user);
user.LastName = "Test B";
dc.SubmitChanges();
}
}
You need to handle object versioning.
An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy.
So, if there's no timestamp member or other 'versioning' mechanism provided there's no way for LINQ to determine whether that data has changed - hence the error you are seeing.
I resolved this issue by adding a timestamp column to my tables but there are other ways around it. Rick Strahl has written some decent articles about exactly this issue.
Also, see this and this for a bit more info.