I have the following object:
WordOccurrence, which has two attributes:
- string word.
- int occurrences.
I would like to do the following, without getting an exeception (-:
WordOccurrence w1 = new WordOccurrence() {Word ="Hey", Occurrence = 1};
WordOccurrence w2 = new WordOccurrence() {Word ="Hey", Occurrence = 1};
now I would like to store the first one , w1, but with w2 to delete him.
session.store(w1);
session.delete(w2); -> gets exeception...
is it possible??
Please try formatting the code in your question, and rephrasing what you are trying to do. It isn't clear if you're expecting that saving w2 should refer to the same object as w1, but they aren't - even if the ID is the same that isn't how Raven will handle the delete. You need to either delete the object you just stored immediately after you call SaveChanges (which I don't understand why you would want to do), or in the more likely scenario Load it at some point later on and then call Delete:
var w1Id = string.empty;
using(session)
{
var w1 = new WordOccurrence { Word="Hey", Occurrence=1};
session.store(w1);
session.SaveChanges();
w1Id = w1.Id;
//if you aren't declaring the Id property for some reason...
w1Id = session.Advanced.GetDocumentId(w1);
}
//somewhere else in the code
using(session)
{
var w1 = session.Load<WordOccurrence>(w1Id);
session.Delete(w1);
session.SaveChanges();
}
Bottom line is that you can't delete something you just told the session to Store, before you even called SaveChanges. If you're trying to undo a Store operation, perhaps because the user hit an undo button, just don't call SaveChanges (if it's the only operation in the session), or use Session.Advanced.Evict(w1) to evit that object from the session.
If you are expecting the Word property to be the Id of the document you can make that happen by customizing the DocumentStore conventions
Related
i have a database containing Song objects. The song class has > 30 properties.
My Music Tagging application is doing changes on a song on the file system.
It then does a lookup in the database using the filename.
Now i have a Song object, which i created in my Tagging application by reading the physical file and i have a Song object, which i have just retrieved from the database and which i want to update.
I thought i just could grab the ID from the database object, replace the database object with my local song object, set the saved id and store it.
But Raven claims that i am replacing the object with a different object.
Do i really need to copy every single property over, like this?
dbSong.Artist = songfromFilesystem.Artist;
dbSong.Album = songfromFileSystem.Album;
Or are there other possibilities.
thanks,
Helmut
Edit:
I was a bit too positive. The suggestion below works only in a test program.
When doing it in my original code i get following exception:
Attempted to associate a different object with id 'TrackDatas/3452'
This is produced by following code:
try
{
originalFileName = Util.EscapeDatabaseQuery(originalFileName);
// Lookup the track in the database
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
if (dbTracks.Count > 0)
{
track.Id = dbTracks[0].Id;
_session.Store(track);
_session.SaveChanges();
}
}
catch (Exception ex)
{
log.Error("UpdateTrack: Error updating track in database {0}: {1}", ex.Message, ex.InnerException);
}
I am first looking up a song in the database and get a TrackData object in dbTracks.
The track object is also of type TrackData and i just put the ID from the object just retrieved and try to store it, which gives the above error.
I would think that the above message tells me that the objects are of different types, which they aren't.
The same error happens, if i use AutoMapper.
any idea?
You can do what you're trying: replace an existing object using just the ID. If it's not working, you might be doing something else wrong. (In which case, please show us your code.)
When it comes to updating existing objects in Raven, there are a few options:
Option 1: Just save the object using the same ID as an existing object:
var song = ... // load it from the file system or whatever
song.Id = "Songs/5"; // Set it to an existing song ID
DbSession.Store(song); // Overwrites the existing song
Option 2: Manually update the properties of the existing object.
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.Artist = song.Artist;
existingSong.Album = song.Album;
Option 3: Dynamically update the existing object:
var song = ...;
var existingSong = DbSession.Load<Song>("Songs/5");
existingSong.CopyFrom(song);
Where you've got some code like this:
// Inside Song.cs
public virtual void CopyFrom(Song other)
{
var props = typeof(Song)
.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance)
.Where(p => p.CanWrite);
foreach (var prop in props)
{
var source = prop.GetValue(other);
prop.SetValue(this, source);
}
}
If you find yourself having to do this often, use a library like AutoMapper.
Automapper can automatically copy one object to another with a single line of code.
Now that you've posted some code, I see 2 things:
First, is there a reason you're using the Advanced.DocumentQuery syntax?
// This is advanced query syntax. Is there a reason you're using it?
var dbTracks = _session.Advanced.DocumentQuery<TrackData, DefaultSearchIndex>().WhereEquals("Query", originalFileName).ToList();
Here's how I'd write your code using standard LINQ syntax:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
track.Id = existingTrackId;
_session.Store(track);
_session.SaveChanges();
}
Finally, #2: what is track? Was it loaded via session.Load or session.Query? If so, that's not going to work, and it's causing your problem. If track is loaded from the database, you'll need to create a new object and save that:
var escapedFileName = Util.EscapeDatabaseQuery(originalFileName);
// Find the ID of the existing track in the database.
var existingTrackId = _session.Query<TrackData, DefaultSearchIndex>()
.Where(t => t.Query == escapedFileName)
.Select(t => t.Id);
if (existingTrackId != null)
{
var newTrack = new Track(...);
newTrack.Id = existingTrackId;
_session.Store(newTrack);
_session.SaveChanges();
}
This means you already have a different object in the session with the same id. The fix for me was to use a new session.
I'm getting the Instance ID of an object from collision_line()
Now that I have this instance, I want to get it's image_angle, but I get an 'unknown variable' message when I try that.
What should I do?
what is the value of this collision_line()? The collision_line() function returns an instance id - however when nothing is found it returns noone (-4).. So you'll have to test for that first:
var inst, imgangle;
inst = collision_line(...);
if (inst != noone) {
imgangle = inst.image_angle;
//etc etc
}
or alternativelly (more cleanly in GM), we can "abuse" the with statement. With executes all following code from the perspective of the given instance id (or for all instances of a certain object when given the object id).
However the value noone will automatically prevent any execution.
var inst, imgangle;
inst = collision_line(...);
with (inst) {
imgangle = image_angle;
//note that we do no longer have to put "inst." before getting a variable
//etc etc
}
I have an excel file that contains a list of UserAccounts. I also havea method to import those UserAccounts and save them into RavenDB. In the excel file I store the Id of the UserAccount Object (useraccounts/55). RavenDB is not assigning the value, I am assigning it. My import is working great.
However,
Later on, I try to save a new UserAccount through the admin panel using the following method:
[HttpPost]
public ActionResult Create(UserAccountViewModel input)
{
// Validation omitted
var model = new UserAccount()
{
Email = input.Email,
FirstName = input.FirstName,
LastName = input.LastName,
Phone = input.Phone,
Username = input.Username,
AuthorizeNetCustomerProfileId = customer.ProfileID,
Password = input.Password,
};
Raven.Store(model);
Raven.SaveChanges();
return RedirectToAction("Index");
}
When I call
Raven.Store(model)
It assigns an Id to the new UserAccount object but it starts at 1. So the first time I try to do this it assigns UserAccounts/1 to my new UserAccount. The issue is that UserAccounts/1 already exists from my import so when I call save changes I am getting an etag exception.
When I run the method again it assigns UserAccounts/2 and so on? Ideas?
The easiest way would be to have a string Id property in your UserAccount class, and assign it a value of "UserAccounts/". That trailing slash is going to ask RavenDB to assign it an ID using an identity-like process, instead of HiLo. Its somewhat slower, but it'll work.
The better way of solving this is by changing the HiLo documents on the server, making them start with the first available range, but thats messier.
In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "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." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);
How do I re-read some (class) Items from the database? I have read them once and made same updates, updates I dont wont to save. Now I need a complete fresh collection of Items from the database.
I have noticed that there are a function called SetForceCacheRefresh, but how do I use it with a CreateCriteria?
// Mats
IList<T> list = null;
using (Repository rep = new Repository())
{
IQuery iqry = rep.Session.CreateQuery(hql);
iqry.SetForceCacheRefresh(true);
list = iqry.List<T>();
}
Note: Before calling List(), set SetForceCacheRefresh(true) to refresh.