I'm trying to write an index that looks at our logs to find entities that are unavailable due to undergoing a part of the process but not a second part of the process which then makes them available. Our import process happens first, then our clean process. Entities that have gone through our clean process are then considered available. I want to essentially find out those entities that have undergone the import process (therefore resetting them) after the last time the cleaning occurred.
I came up with this potential map:
AddMap<EntityLog>(docs => docs.Where(doc => doc.Details != null)
.Where(doc => doc.Details.Any(d => d.QueueMessage != null && d.QueueMessage.JobsToDo.Contains(JobType.Import)
&& d.Finished != null))
.Where(doc => doc.Details.Any(d => d.QueueMessage != null && d.QueueMessage.JobsToDo.Contains(JobType.Clean)
&& d.Finished != null))
.Where(doc => doc.Details.FindLastIndex(d => d.QueueMessage.JobsToDo.Contains(JobType.Clean) && d.Finished != null) <
doc.Details.FindLastIndex(d => d.QueueMessage.JobsToDo.Contains(JobType.Import) && d.Finished != null))
.Select(found => new
{
found.EntityID,
User = found.Details.Select(d => d.User)
}));
It compiles in VS, but when I try to make this index, I get the following error:
CS1977 "Cannot use a lambda expression as an argument to a dynamically dispatched operation without first casting it to a delegate or expression tree type"
From some selective commenting, it seems to be the line where I'm using FindLastIndex. And from research, I know that this compiler error comes in when you're trying to do stuff with untyped objects that you're not supposed to. From looking at the output Raven-made code, it uses dynamic a lot, so I'm guessing its something related to that. I did try using this as an alternative to that line:
.Where(doc => Array.FindLastIndex(doc.Details.ToArray(), d => d.QueueMessage.JobsToDo.Contains(JobType.Clean) && d.Finished != null) <
Array.FindLastIndex(doc.Details.ToArray(), d => d.QueueMessage.JobsToDo.Contains(JobType.Import) && d.Finished != null))
But no joy, I still get the same error message.
How do I get this code to work? Or do I need to take a completely different approach?
Any help appreciated.
FindLastIndex is a method on List, but we don't know what the type is on the server side, so that isn't available.
Use the extension method on IEnumerable, instead.
Just a create a get only property on your EntityLog that uses the FindLastIndex then the results of that information is what will be serialized to the database and easy to search upon.
This is a rather common pattern I follow when working with Lists.
public class Foo {
public List<Phones> Phones { get;set; }
public Phone PrimaryPhone {
get { return Phones.SingleOrDefault(x=> x.Primary == true)) }
}
}
Note, if EntityLog is an already existing document collection, you will need to Load each log and save it back to the database.
Related
I am trying to manually clear the level 2 cache for a specific region. I found the method posted in answer to this question. While this is working to clear my entities, for some reason the querycache is not getting cleared. This results in a separate query for each entity the next time the entities are retrieved from the database. If does work when I call sessionFactory.EvictQueries() without any parameters. It is only not working when I am passing in a specific region name. Any ideas as to what is going wrong?
Code is from the above link:
private void ClearRegion(string regionName)
{
_sessionFactory.EvictQueries(regionName);
foreach (var collectionMetaData in _sessionFactory.GetAllCollectionMetadata().Values)
{
var collectionPersister = collectionMetaData as NHibernate.Persister.Collection.ICollectionPersister;
if (collectionPersister != null)
{
if ((collectionPersister.Cache != null) && (collectionPersister.Cache.RegionName == regionName))
{
_sessionFactory.EvictCollection(collectionPersister.Role);
}
}
}
foreach (var classMetaData in _sessionFactory.GetAllClassMetadata().Values)
{
var entityPersister = classMetaData as NHibernate.Persister.Entity.IEntityPersister;
if (entityPersister != null)
{
if ((entityPersister.Cache != null) && (entityPersister.Cache.RegionName == regionName))
{
_sessionFactory.EvictEntity(entityPersister.EntityName);
}
}
}
}
Caching is working and verified using NHProfiler.
Ok, so I figured out my issue. I did not realize that it is necessary to specify a cache region when querying the data, aside from specifying it in the entity mapping. After adding .CacheRegion("regionName") to my queries everything works. By not adding the region when querying, it was going into the query cache without a region name. That is why it worked when I called.EvictQueries() without a region name parameter.
To sum it up, it is necessary to add the region name when mapping the entities (.Region("regionName") when using Fluent) and when querying with isession using .CacheRegion("regionName").
Thank you for you responses.
I have a complex type called account, which contains a list of licenses.
Licenses in turn contains a list of domains (a domain is a simple id + url string).
In my repository I have this code
public void SaveLicense(int accountId, License item)
{
Account account = GetById(accountId);
if (account == null)
{
return;
}
if (item.Id == 0)
{
account.Licenses.Add(item);
}
else
{
ActiveContext.Entry(item).State = EntityState.Modified;
}
ActiveContext.SaveChanges();
}
When I try to save an updated License (with modified domains) what happens is that strings belonging straight to the license get updated just fine.
However no domains get updated.
I should mention that what I have done is allow the user to add and remove domains in the user interface. Any new domains get id=0 and any deleted domains are simply not in the list.
so what I want is
Any domains that are in the list and database and NOT changed - nothing happens
Any domains that are in the list and database, but changed in the list - database gets updated
Any domains with id=0 should be inserted (added) into database
Any domains NOT in the list but that are in the database should be removed
I have played a bit with it with no success but I have a sneaky suspicion that I am doing something wrong in the bigger picture so I would love tips on if I am misunderstanding something design-wise or simply just missed something.
Unfortunately updating object graphs - entities with other related entities - is a rather difficult task and there is no very sophisticated support from Entity Framework to make it easy.
The problem is that setting the state of an entity to Modified (or generally to any other state) only influences the entity that you pass into DbContext.Entry and only its scalar properties. It has no effect on its navigation properties and related entities.
You must handle this object graph update manually by loading the entity that is currently stored in the database including the related entities and by merging all changes you have done in the UI into that original graph. Your else case could then look like this:
//...
else
{
var licenseInDb = ActiveContext.Licenses.Include(l => l.Domains)
.SingleOrDefault(l => l.Id == item.Id)
if (licenseInDb != null)
{
// Update the license (only its scalar properties)
ActiveContext.Entry(licenseInDb).CurrentValus.SetValues(item);
// Delete domains from DB that have been deleted in UI
foreach (var domainInDb in licenseInDb.Domains.ToList())
if (!item.Domains.Any(d => d.Id == domainInDb.Id))
ActiveContext.Domains.Remove(domainInDb);
foreach (var domain in item.Domains)
{
var domainInDb = licenseInDb.Domains
.SingleOrDefault(d => d.Id == domain.Id);
if (domainInDb != null)
// Update existing domains
ActiveContext.Entry(domainInDb).CurrentValus.SetValues(domain);
else
// Insert new domains
licenseInDb.Domains.Add(domain);
}
}
}
ActiveContext.SaveChanges();
//...
You can also try out this project called "GraphDiff" which intends to do this work in a generic way for arbitrary detached object graphs.
The alternative is to track all changes in some custom fields in the UI layer and then evaluate the tracked state changes when the data get posted back to set the appropriate entity states. Because you are in a web application it basically means that you have to track changes in the browser (most likely requiring some Javascript) while the user changes values, adds new items or deletes items. In my opinion this solution is even more difficult to implement.
This should be enough to do what you are looking to do. Let me know if you have more questions about the code.
public void SaveLicense(License item)
{
if (account == null)
{
context.Licenses.Add(item);
}
else if (item.Id > 0)
{
var currentItem = context.Licenses
.Single(t => t.Id == item.Id);
context.Entry(currentItem ).CurrentValues.SetValues(item);
}
ActiveContext.SaveChanges();
}
I have a simple GET method, which returns IQueryable, and has some preconditions on query:
[Queryable(HandleNullPropagation = HandleNullPropagationOption.False)]
public IQueryable<Message> Get()
{
using (var session = RavenStore.GetSession())
{
var messages = session.Query<Message>().Where(x => x.TargetUserId == this.User.Identity.Name || x.SourceUserId == this.User.Identity.Name);
return messages;
}
}
This is RavenDB, btw. The issue I'm having is that upon execution the user id is replaced with "[EMPTY_STRING]", so the actual query its running is this:
'TargetUserId:[[EMPTY_STRING]] OR SourceUserId:[[EMPTY_STRING]]' on
index .....
which is obviously wrong.
If I'm returning List instead of IQueriable - it works fine, so something later in the pipeline changes the query. Does anyone have any insight on how to make this work ?
It should work when the values are copied to a local variable first:
var userName = this.User.Identity.Name;
return session.Query<Message>()
.Where(x => x.TargetUserId == userName ||
x.SourceUserId == userName);
This is because by the time the query is executed, the Raven Client query translator can't resolve the objects expressed in the predicate. By copying them into a local variable, you are passing a constant value into the expression.
I believe this is related to closures. Perhaps someone with more direct knowledge of expression trees can explain better in comments.
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'm using Fluent NHibernate in order to auto map my entities.
This is the code I'm using for the auto mapping:
new AutoPersistenceModel()
.AddEntityAssembly(Assembly.GetAssembly(typeof(Entity)))
.Where(type => type.Namespace.Contains("Domain") && type.BaseType != null && type.BaseType.Name.StartsWith("DomainEntity") && type.BaseType.IsGenericType == true)
.WithSetup(s => s.IsBaseType = (type => type.Name.StartsWith("DomainEntity") && type.IsGenericType == true))
.ConventionDiscovery.Add(
ConventionBuilder.Id.Always(x => x.GeneratedBy.Increment())
);
This works just fine. But now I need to have Eager Loading in one single object of my domain. Found this answer. But when I add the line .ForTypesThatDeriveFrom<IEagerLoading>(map => map.Not.LazyLoad()) to the code and run it I get the following exception:
Error while trying to build the Mapping Document for IEagerLoading
Notice that I'm using an interface (IEagerLoading) to mark the objects that I want eager load.
Can anyone help how to do this? Remember that I want to keep the auto mapping functionality.
Thanks
The problem you're hitting is that ForTypesThatDeriveFrom<T> is a bit misleadingly named, and that it really means ForMappingsOf<T>, so it's trying to find a ClassMap<IEagerLoading> which obviously doesn't exist.
I believe you should be able to handle this with a custom IClassConvention. This is off the top of my head, but should work:
public class EagerLoadingConvention : IClassConvention
{
public bool Accept(IClassMap target)
{
return GetType().GetInterfaces().Contains(typeof(IEagerLoading));
}
public void Apply(IClassMap target)
{
target.Not.LazyLoad();
}
}