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.
Related
I was writing tests using SQLite in-memory database with XUnit and ASP.NET Core 3.1 and found strange behavior.
Lets say that we have User model and we want to change property IsActive to false:
var u = new User {Id = Guid.NewGuid(), IsActive = true};
_db.Users.Add(u);
_db.SaveChanges();
u.IsActive = false;
// Returns false
var isActive = _db.Users.Single(x => x.Id == u.Id).IsActive;
// Returns true
var isActiveNoTracking = _db.Users.AsNoTracking().Single(x => x.Id == u.Id).IsActive;
// Fails.
Assert.Equal(isActive, isActiveNoTracking);
I get different result depending if AsNoTracking() is called or not. Why is this happening? Isn't AsNoTracking() supposed to stop tracking changes made on fetched object, not to mess with data that was already changed?
If I call SaveChanges() after changing the property then it is all good (as expected):
var u = new User {Id = Guid.NewGuid(), IsActive = true};
_db.Users.Add(u);
_db.SaveChanges();
u.IsActive = false;
_db.SaveChanges();
// Returns false
var isActive = _db.Users.Single(x => x.Id == u.Id).IsActive;
// Returns false
var isActiveNoTracking = _db.Users.AsNoTracking().Single(x => x.Id == u.Id).IsActive;
// Success.
Assert.Equal(isActive, isActiveNoTracking);
So I am confused, I'm not sure when SQLite in-memory actually commits changes. Sometimes you can fetch changes from db without calling SaveChanges() but sometimes you cannot.
Here is code related to db
public class SqliteInMemoryAppDbContext : AppDbContext
{
public SqliteInMemoryAppDbContext(IConfiguration configuration) : base(configuration)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
var connection = new SqliteConnection("DataSource=:memory:");
connection.Open();
options.UseSqlite(connection);
}
}
// I create db context for each test like this and dispose it after each test.
var _db = new SqliteInMemoryAppDbContext(null);
_db.Database.EnsureDeleted();
_db.Database.EnsureCreated();
So I am confused, I'm not sure when SQLite in-memory actually commits changes. Sometimes you can fetch changes from db without calling SaveChanges() but sometimes you cannot.
This is impossible. In order for something to be saved to the DB you need to call SaveChanges. What happens here is that you see local objects and you assume that they are stored in your DB. I generally suggest that you use a a DB query tool to learn how it works because it can be difficult at first.
Entity Framework has some local objects that it stores. For example at your first query.
// returns true, because it checks the db due to no tracking
_db.Users.AsNoTracking().Where(x => x.IsActive).OrderBy(x=>x.Username).ToList()[0].IsActive
// returns false, it finds the local reference
_db.Users.Where(x => x.IsActive).OrderBy(x=>x.Username).ToList()[0].IsActive
As you can see from the comments above it has different behavior based on the commands. It's not about when changes are saved to the db. This happens only if you call SaveChanges. What you are confused is for when the 'queries' you write with EF look at the DB or locally.
Generally for SQL at least I like to work with SQL profiler to see what queries EF sends to the Database. For example in your case you will have a query with where and order by send to the db.
EDIT:
About how to understand when the db is called or not i suggest reading here.
To summarize AsNoTracking always creates the new entity which means that it will look in the db for it. Instead the other commands in your example first look locally for the object.
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.
Given 1000 documents with a complex data structure. for e.g. a Car class that has three properties, Make and Model and one Id property.
What is the most efficient way in C# to push these documents to raven db (preferably in a batch) without having to query the raven collection individually to find which to update and which to insert. At the moment I have to going like so. Which is totally inefficient.
note : _session is a wrapper on the IDocumentSession where Commit calls SaveChanges and Add calls Store.
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
var page = 0;
const int total = 30;
do
{
var paged = sales.Skip(page*total).Take(total);
if (!paged.Any()) return;
foreach (var sale in paged)
{
var current = sale;
var existing = _session.Query<Sale>().FirstOrDefault(s => s.Id == current.Id);
if (existing != null)
existing = current;
else
_session.Add(current);
}
_session.Commit();
page++;
} while (true);
}
Your session code doesn't seem to track with the RavenDB api (we don't have Add or Commit).
Here is how you do this in RavenDB
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
sales.ForEach(session.Store);
session.SaveChanges();
}
Your code sample doesn't work at all. The main problem is that you cannot just switch out the references and expect RavenDB to recognize that:
if (existing != null)
existing = current;
Instead you have to update each property one-by-one:
existing.Model = current.Model;
existing.Make = current.Model;
This is the way you can facilitate change-tracking in RavenDB and many other frameworks (e.g. NHibernate). If you want to avoid writing this uinteresting piece of code I recommend to use AutoMapper:
existing = Mapper.Map<Sale>(current, existing);
Another problem with your code is that you use Session.Query where you should use Session.Load. Remember: If you query for a document by its id, you will always want to use Load!
The main difference is that one uses the local cache and the other not (the same applies to the equivalent NHibernate methods).
Ok, so now I can answer your question:
If I understand you correctly you want to save a bunch of Sale-instances to your database while they should either be added if they didn't exist or updated if they existed. Right?
One way is to correct your sample code with the hints above and let it work. However that will issue one unnecessary request (Session.Load(existingId)) for each iteration. You can easily avoid that if you setup an index that selects all the Ids of all documents inside your Sales-collection. Before you then loop through your items you can load all the existing Ids.
However, I would like to know what you actually want to do. What is your domain/use-case?
This is what works for me right now. Note: The InjectFrom method comes from Omu.ValueInjecter (nuget package)
private void PublishSalesToRaven(IEnumerable<Sale> sales)
{
var ids = sales.Select(i => i.Id);
var existingSales = _ravenSession.Load<Sale>(ids);
existingSales.ForEach(s => s.InjectFrom(sales.Single(i => i.Id == s.Id)));
var existingIds = existingSales.Select(i => i.Id);
var nonExistingSales = sales.Where(i => !existingIds.Any(x => x == i.Id));
nonExistingSales.ForEach(i => _ravenSession.Store(i));
_ravenSession.SaveChanges();
}
I have a problem retrieving items from a fairly large list. I can quickly and easily retrieve items from a small list with more or less 50 items, but when I try to retrieve items from a list containing more or less 4600 items, the sqlsever.exe process spikes for the duration of the request, but the items are never retrieved. If have set up the web applications throttling settings, so it can’t be that which is causing the problem. Here is the code that I originally used to retrieve the items. There is really nothing special to it.
using (SPSite site = new SPSite(siteUrl))
{
using (SPWeb web = site.OpenWeb())
{
SPList list = web.Lists[uid.ToString()];
SPListItemCollection itemCollection = list.Items;
foreach (SPListItem i in itemCollection) //This is where the code stops responding
{
//Use list items
}
}
}
After that didn’t work, I tried a couple of other methods to retrieve the items from the list. Here is the code:
SPList list = web.Lists[uid.ToString()];
SPQuery query = new SPQuery();
query.Query = "";
query.QueryThrottleMode = SPQueryThrottleOption.Override;
SPListItemCollection itemCollection = list.GetItems(query);
//The code stops here
//I added this part for interest sake, i wanted to if it was the looping that caused the problem
//It seems the when you try to access properties of the item collection that the problem occurs
int itemCount = itemCollection.Count;
foreach (SPListItem i in itemCollection)
{
//Use list items
}
I also tried:
SPList list = web.Lists[uid.ToString()];
SPListItemCollectionPosition pos;
DataTable dt = list.GetDataTable(new SPQuery(), SPListGetDataTableOptions.None, out pos); //The code stops responding here
foreach (DataRow i in dt.Rows)
{
//Use data rows
}
Does anyone know what could be causing this problem?
Thank you in advance!
After a very long struggle we found a solution.
We found this post:
http://trycatch.be/blogs/tom/archive/2009/04/22/never-turn-off-quot-auto-create-amp-auto-update-statistics-quot.aspx
We tested it and it worked!!!
So all we had to do was switch "Auto create statistics" and "Auto update statistics" to true, and to problem was solved
Thanks for all the replys
You'll kill your server if you always try to retrieve all items either through list.Items, list.GetItems(query) (with an empty query).
You need to define a relevant query and specify the amount of results you want to retrieve through the RowLimit property of your SPQuery
SPQuery query = new SPQuery();
query.Query = "<OrderBy><FieldRef Name='Title' /></OrderBy>"; // any relevant query here
query.RowLimit = 50;
Failing to do so will load all the items in memory prior to any other operation. You will probably kill your application pool available memory or your sql server memory when it will try to load all these data !
Hope that helped.
Try to get data using CAML query.
Once you have data you can put that in SPQuery.
Try to load the items not all at the same time. You can load them bulk wise (page wise) with the help of SPQuery.ListItemCollectionPosition (http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spquery.listitemcollectionposition.aspx).
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);