I have a database with a bunch of reference tables like States, Languages, etc.. I would like to be able to cache these tables and then use those types in any ObjectContext I want. So in pseudo code I want to be able to do something like this
var db1 = new PatientObjectContext();
Cache.CacheStates(db1.States.ToList())
var person = new Person { State = Cache.GetState("PA")} ;
var db2 = new PatientObjectContext();
db2.People.Add(person)
db2.SaveChanges();
I saw this blog post (http://blogs.msdn.com/b/alexj/archive/2009/04/22/tip-14-caching-entity-framework-reference-data.aspx). But this didn't apply to me because I think it is not using POCO's. When I try to attach the cached object to the objectContext, I get an exception because an object with that primary key is already in that contexts states collection.
It seems that caching reference tables should be a pretty common problem, but I cant seem to find any straight forward solutions for this.
Alex's post is every bit as relevant to POCO entities as to non-POCOs. Why do you think it isn't?
I figured out what I was doing wrong. Before attaching the cloned cached object to the new context I needed to make sure that it is not already attached. Because attaching the same object 2 times was throwing an exception. So I found some code that lets me check if an Item is already in the context before attaching it.
Is is possible to check if an object is already attached to a data context in Entity Framework?
Related
I have an EF Core database context set up that has a particular set of objects. I want to be able to delete these objects from my database via their Id. However I don't want to query the database and extract these objects as they're quite big and I'm trying to avoid the performance overhead.
Looking at this link it seems like a simple enough procedure. Find your Ids, create temporary objects of the same type and then attach them to the context, then remove them.
However when I do this, I get the following exception.
The instance of entity type 'Type' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked.
I assume this means that because there is already an instance of the object being tracked, I can't then load a second instance of the object into memory using the same id.
How then can I delete these objects using their Id? Loading the original database objects into memory is completely out of the question.
var policySetIdsToDelete = _configDbContext.PolicySets
.Where(ps => ps.SerialNo == serialNumber.ToUpper())
.OrderByDescending(ps => ps.Created).Skip(5).Select(ps => ps.Id.ToString()).ToList();
foreach (var id in policySetIdsToDelete)
{
var policySet = new PolicySet
{
Id = new Guid(id)
};
_configDbContext.PolicySets.Attach(policySet);
_configDbContext.PolicySets.Remove(policySet);
}
_configDbContext.SaveChanges();
You can use:
_configDbContext.Database.ExecuteSqlCommand
or try
_configDbContext.PolicySets.RemoveRange
You can inspire also from this article Entity Framework. Delete all rows in table
If I have Book object which has a child collection of Comments, Can I update the Book and list of Comments together with entity framework?
I have tried :
_context.Books.Attach(book);
_context.ObjectStateManager.ChangeObjectState(book, EntityState.Modified);
_context.SaveChanges();
with no luck...
getting the following error on the first line:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
More than likely you have a circular dependency (Books has a foreign key reference to Comments, and Comments back to Books). In this case, the UpdateTranslator within EF is unable to determine the dependency order. As far as I can tell, in this model of development, there is no way to pass a hint to EF to indicate what the order is.
The most common way to solve this (that I have seen) is to do a two-phase commit. Make a change to the Book, save it, then make a change to Comments, and save that. I have found that using the Code First approach allows you to be more specific about the relationships and thereby fix many of the problems that I've had.
Edit:
Here's an example:
using (var context = new BookContext())
{
book.Title = "This is the new title";
context.SaveChanges();
book.Comments.Add(new Comment("This is a comment"));
context.SaveChanges();
}
If there is a circular dependency, you could not do the above with a single call to SaveChanges.
I'm playing around with NHibernate 3.0. So far things are pretty cool. I'm trying to attach an entity that wasn't detached previously:
var post = new Post(){ Id = 2 };
session.Update(post); // Thought this would work but it doesn't.
post.Title = "New Title After Update";
session.Flush();
What I'm trying to write is the following:
var post = new Post(){ Id = 2 };
session.Attach(post);
post.Title = "New Title After Update";
session.Flush(); // Sql should be something like: UPDATE Post SET Title='New Title After Update' WHERE Id=2
Is this possible so that only Title gets updated? This is currently possible in EntityFramework. I'd like to not have to load Post from the database when I just need to update a few properties. Also, I'm trying to avoid a method call that would create the object... since it's moving away from an object oriented approach in my opinion.
EDIT: I know about using transactions, I just used Flush() to make the code simple. Ok so I think we're sort of getting on the right track for what I'm trying to achieve. I'd like to be able to create an entity with a known Id using the constructor, like I have in the 2nd code block above. I don't want to have to make a call to Get<T> or Load<T> since it feels rather wrong constructing objects like this that already exist in the database. For example, in Entity Framework I can write the 2nd code example and it will "just work". It only updates the Title property.
You can session.Save() or session.SaveOrUpdate()
update
Okay, I think I see now what you are trying to do. You are trying to update a single property on a Post that was previously persisted, not a new Post, and to do that you're instantiating a new Post and giving it the Id of one in the database.
I'm not sure what you mean when you say you're trying to avoid a method call that would create the object, but the way to do this with NHibernate is this:
var post = session.Load<Post>(2);
post.Title = "New Title";
session.SaveOrUpdate(post);
In general, you should not be calling Flush() on your sessions.
The important thing to note here is the use of session.Load. Calling Load with an id in and of itself does not load the entity from the database. The entity's property values will only be loaded when/if you access them.
Of course, in this scenario, I believe that NHibernate will load the properties for the Post, (but not collections unless you've specified an eager fetch mode), and that makes sense (frankly, I don't understand why EF would not load the entity). What if the setter for your Title property does something important, like check it against the existing title, validate the title's length, check your credentials, or update another property? Simply sending an UPDATE to the database isn't sufficient.
It's possible to only update changed properties by setting dynamic-update in the mapping. However, as far as I know, it is not possible (without reverting to SQL) to perform an update without retrieving the object from the database at some point.
Use the Merge method. You have to create a new instance variable to accept the attached entity = nhibernate will not do anything else with your detached instance.
var post = new Post(){ Id = 2 };
post.Title = "New Title After Update";
// Must create a new instance to hold final attached entity
var attachedPost = session.Merge(post);
session.Update(attachedPost);
session.Flush();
// Use attachedPost after this if still needed as in session entity
That covers the "attach" functionality you are looking for, but I don't see how you are going to be able to only update the one property. if the object instance has not been populated from the database, the properties will be different. Dynamic mapping will not solve this - NHibernate sees the properties as "updated" to a bunch of nulls, empty strings.
Gotta say, you are creating a new instance but what you are actually doing is updating an existing instance. You are working directly with IDs not objects. And you are setting a single property and now have an instance potentially hanging around and doing more things but it has not enforced any invariants and may in fact bear no resemblence to the real deal other than the id property...
It all feels pretty anti-object oriented to me personally.
I have a Linq object, and I want to make changes to it and save it, like so:
public void DoSomething(MyClass obj) {
obj.MyProperty = "Changed!";
MyDataContext dc = new MyDataContext();
dc.GetTable<MyClass>().Attach(dc, true); // throws exception
dc.SubmitChanges();
}
The exception is:
System.InvalidOperationException: 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.
It looks like I have a few choices:
put a version member on every one of my Linq classes & tables (100+) that I need to use in this way.
find the data context that originally created the object and use that to submit changes.
implement OnLoaded in every class and save a copy of this object that I can pass to Attach() as the baseline object.
To hell with concurrency checking; load the DB version just before attaching and use that as the baseline object (NOT!!!)
Option (2) seems the most elegant method, particularly if I can find a way of storing a reference to the data context when the object is created. But - how?
Any other ideas?
EDIT
I tried to follow Jason Punyon's advice and create a concurrency field on on table as a test case. I set all the right properties (Time Stamp = true etc.) on the field in the dbml file, and I now have a concurrency field... and a different error:
System.NotSupportedException: 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.
So what the heck am I supposed to attach, then, if not an existing entity? If I wanted a new record, I would do an InsertOnSubmit()! So how are you supposed to use Attach()?
Edit - FULL DISCLOSURE
OK, I can see it's time for full disclosure of why all the standard patterns aren't working for me.
I have been trying to be clever and make my interfaces much cleaner by hiding the DataContext from the "consumer" developers. This I have done by creating a base class
public class LinqedTable<T> where T : LinqedTable<T> {
...
}
... and every single one of my tables has the "other half" of its generated version declared like so:
public partial class MyClass : LinqedTable<MyClass> {
}
Now LinqedTable has a bunch of utility methods, most particularly things like:
public static T Get(long ID) {
// code to load the record with the given ID
// so you can write things like:
// MyClass obj = MyClass.Get(myID);
// instead of:
// MyClass obj = myDataContext.GetTable<MyClass>().Where(o => o.ID == myID).SingleOrDefault();
}
public static Table<T> GetTable() {
// so you can write queries like:
// var q = MyClass.GetTable();
// instead of:
// var q = myDataContext.GetTable<MyClass>();
}
Of course, as you can imagine, this means that LinqedTable must somehow be able to have access to a DataContext. Up until recently I was achieving this by caching the DataContext in a static context. Yes, "up until recently", because that "recently" is when I discovered that you're not really supposed to hang on to a DataContext for longer than a unit of work, otherwise all sorts of gremlins start coming out of the woodwork. Lesson learned.
So now I know that I can't hang on to that data context for too long... which is why I started experimenting with creating a DataContext on demand, cached only on the current LinqedTable instance. This then led to the problem where the newly created DataContext wants nothing to do with my object, because it "knows" that it's being unfaithful to the DataContext that created it.
Is there any way of pushing the DataContext info onto the LinqedTable at the time of creation or loading?
This really is a poser. I definitely do not want to compromise on all these convenience functions I've put into the LinqedTable base class, and I need to be able to let go of the DataContext when necessary and hang on to it while it's still needed.
Any other ideas?
Updating with LINQ to SQL is, um, interesting.
If the data context is gone (which in most situations, it should be), then you will need to get a new data context, and run a query to retrieve the object you want to update. It's an absolute rule in LINQ to SQL that you must retrieve an object to delete it, and it's just about as iron-clad that you should retrieve an object to update it as well. There are workarounds, but they are ugly and generally have lots more ways to get you in trouble. So just go get the record again and be done with it.
Once you have the re-fetched object, then update it with the content of your existing object that has the changes. Then do a SubmitChanges() on the new data context. That's it! LINQ to SQL will generate a fairly heavy-handed version of optimistic concurrency by comparing every value in the record to the original (in the re-fetched) record. If any value changed while you had the data, LINQ to SQL will throw a concurrency exception. (So you don't need to go altering all your tables for versioning or timestamps.)
If you have any questions about the generated update statements, you'll have to break out SQL Profiler and watch the updates go to the database. Which is actually a good idea, until you get confidence in the generated SQL.
One last note on transactions - the data context will generate a transaction for each SubmitChanges() call, if there is no ambient transaction. If you have several items to update and want to run them as one transaction, make sure you use the same data context for all of them, and wait to call SubmitChanges() until you've updated all the object contents.
If that approach to transactions isn't feasible, then look up the TransactionScope object. It will be your friend.
I think 2 is not the best option. It's sounding like you're going to create a single DataContext and keep it alive for the entire lifetime of your program which is a bad idea. DataContexts are lightweight objects meant to be spun up when you need them. Trying to keep the references around is also probably going to tightly couple areas of your program you'd rather keep separate.
Running a hundred ALTER TABLE statements one time, regenerating the context and keeping the architecture simple and decoupled is the elegant answer...
find the data context that originally created the object and use that to submit changes
Where did your datacontext go? Why is it so hard to find? You're only using one at any given time right?
So what the heck am I supposed to attach, then, if not an existing entity? If I wanted a new record, I would do an InsertOnSubmit()! So how are you supposed to use Attach()?
You're supposed to attach an instance that represents an existing record... but was not loaded by another datacontext - can't have two contexts tracking record state on the same instance. If you produce a new instance (ie. clone) you'll be good to go.
You might want to check out this article and its concurrency patterns for update and delete section.
The "An entity can only be attached as modified without original state if it declares a version member" error when attaching an entitity that has a timestamp member will (should) only occur if the entity has not travelled 'over the wire' (read: been serialized and deserialized again). If you're testing with a local test app that is not using WCF or something else that will result in the entities being serialized and deserialized then they will still keep references to the original datacontext through entitysets/entityrefs (associations/nav. properties).
If this is the case, you can work around it by serializing and deserializing it locally before calling the datacontext's .Attach method. E.g.:
internal static T CloneEntity<T>(T originalEntity)
{
Type entityType = typeof(T);
DataContractSerializer ser =
new DataContractSerializer(entityType);
using (MemoryStream ms = new MemoryStream())
{
ser.WriteObject(ms, originalEntity);
ms.Position = 0;
return (T)ser.ReadObject(ms);
}
}
Alternatively you can detach it by setting all entitysets/entityrefs to null, but that is more error prone so although a bit more expensive I just use the DataContractSerializer method above whenever I want to simulate n-tier behavior locally...
(related thread: http://social.msdn.microsoft.com/Forums/en-US/linqtosql/thread/eeeee9ae-fafb-4627-aa2e-e30570f637ba )
You can reattach to a new DataContext. The only thing that prevents you from doing so under normal circumstances is the property changed event registrations that occur within the EntitySet<T> and EntityRef<T> classes. To allow the entity to be transferred between contexts, you first have to detach the entity from the DataContext, by removing these event registrations, and then later on reattach to the new context by using the DataContext.Attach() method.
Here's a good example.
When you retrieve the data in the first place, turn off object tracking on the context that does the retrieval. This will prevent the object state from being tracked on the original context. Then, when it's time to save the values, attach to the new context, refresh to set the original values on the object from the database, and then submit changes. The following worked for me when I tested it.
MyClass obj = null;
using (DataContext context = new DataContext())
{
context.ObjectTrackingEnabled = false;
obj = (from p in context.MyClasses
where p.ID == someId
select p).FirstOrDefault();
}
obj.Name += "test";
using (DataContext context2 = new ())
{
context2.MyClasses.Attach(obj);
context2.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, obj);
context2.SubmitChanges();
}
I am trying to insert a record. This code worked but has stopped working I don't know why. Here is the code:
using (SAASDataContext dc = new SAASDataContext())
{
tblAssessment a2 = new tblAssessment();
a2.AssessmentCentreId = centreId;
a2.AttemptNumber = 1;
dc.tblAssessments.InsertOnSubmit(a2);
dc.SubmitChanges();
CurrentAssessmentId = a2.AssessmentId;
}
The code compiles but throws the exception below on the dc.SubmitChanges(); line.
Exception thrown:
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.
Notes:
AssessmentCentreId is a foreign key on tblCentre, centreId is a valid existing centre id,
AssessmentCentreId and AttemptNumber are the only not null fields all other columns allow nulls.
I have googled but all the results seem to pertain to people trying to attach entities pulled from other disconnected DataContext's I'm not doing that so I'm stumped.
UPDATE:
Adding
dc.DeferredLoadingEnabled = false;
at the top of the using block makes it work, but I'd like to know why coz I have no idea at the moment sufficiently advanced technology being indistinguishable from magic right now :)
This was bugging me as well. I did some searching and found a lot of confusion and some sloppy work-arounds regarding detached entities. I then found a nice solution on codeplex that has solved the immediate issue and has greatly extended linq2sql functionality. Its a small class thats really easy to implement and forms an EntityBase for your projects dbml's.
Here is the offical description and link.
LINQ to SQL Entity Base is a simple base class that is primarily designed to support LINQ to SQL in a disconnected way, which is one of the shortcomings of the LINQ to SQL technology at the present time. This is highly useful in an n-Tier, distributed or ASP.NET environment where disconnected functionality is relavent.
http://linq2sqleb.codeplex.com/
Hope this helps anyone who is having similar.
Jerome Vernon
The issue is that the Centre object doesn't exist in the context. It's in the db, but not "in your hand" with a2. Try this:
a2.AssessmentCentre = dc.AssessmentCentres.SingleOrDefault(
d=>d.AssessmentCentreId.Equals(centreId));
The AssessmentCentre object will then exist in the context, which means there will be no problem with attaching it to a2.
If these are the only non-null columns, where is the primary key? LINQ to SQL requires a primary key to insert data. If you're using AssessmentCentreId on its own or along with AttemptNumber as a composite, are you sure you're not trying to insert a duplicate key?