NHibernate Projections (QueryOver.SelectList) limits - nhibernate

I'm new on stackoverflow and i hope this question will be appreciated.
Simply said: I select everything from table x left outer join table y. Table x has way too many columns so i make new object x. This object is used for the Projection. I can project every single column of table x i want. But when i try to project/select an collection (the collection of table y) then i get the same error: 'Index was outside the bounds of the array'.
My question: Does NHibernate support to select/project a collection at all? Because i've seen this question multiple times by searching on google (and stackoverflow), but none of those questions were answered.
Code example:
public class X
{
public virtual int ID { get; set; }
public virtual int IDontNeedMoreInfoAboutClassXItTakesToMuchTimeToRetrieve { get; set; }
public virtual IList<Y> YCollection { get; set; }
}
public class Y
{
public virtual int YID { get; set; }
}
public class XRepository{
public ISession Session {get; set;}
public IList<X> Get()
{
X xAlias = null;
Y yAlias = null;
X resultAlias = null;
return Session.QueryOver<X>()
.JoinAlias(() => xAlias.YCollection, () => yAlias, JoinType.LeftOuterJoin)
.SelectList(list => list
.SelectGroup(() => xAlias.ID).WithAlias(() => resultAlias.ID)
.SelectGroup(() => xAlias.YCollection).WithAlias(() => resultAlias.YCollection)) // Index was outside the bounds of the array
.TransformUsing(Transformers.AliasToBean<X>()).List<X>();
}
}

the results returned have to be grouped with the contents intact (so sql is out since it can only aggregate over the grouped items) but NHibernate can only do this for m,apped entities because there it knows the identity to group by. It is simple to do it manually
Y yAlias = null;
var tempResults = Session.QueryOver<X>()
.JoinAlias(x => x.YCollection, () => yAlias, JoinType.LeftOuterJoin)
.SelectList(list => list
.Select(() => xAlias.Id)
.Select(() => xAlias.Name)
...
.Select(() => yAlias.Id)
...
.ToList<object[]>()
List<X> results = new List<X>(100); // switch to Hashset if too slow and order does not matter
foreach(var row in tempResults)
{
Y y = new Y { Id = (long)row[4], ... };
X x = results.FirstOrDefault(x => x.Id == (long)row[0]));
if (x != null)
x.YCollection.Add(y);
else
results.Add(new X { Id = (long)row[0], ..., YCollection = { y } };
}
return results;

Related

Raven DB filter on subset of array items and sort on the cheapest of the filter results items

Assuming i have an parent class that I filter on various properties, one of which is a property that is an array of items .
Now say that i want to only return the parent item if my array of items as above a min value and below a max value ...that's fine i can work that bit out;
What if i then want to then sort on the filtered result set of those items
I made a c# fiddle example to show what im trying to achieve :
https://dotnetfiddle.net/mV4d28
(note that foo2 is returned first even though foo1 has items in its array that are less that those in foo2)
As i need to do this using a index i need the index to be able to compute the order by based on the filter criteria used in my query.
I know elasticsearch has an inner hits function that dose this and mongo has pipelines which also dose this so im sure Raven must have a way of doing this too ?
I was hoping using just index and a transform with prams i could achieve this so I tried it:
my index and transform look like this
public class familyTransfrom : AbstractTransformerCreationTask<ParentItem>
{
public class Result : ParentItem{
public double[] ChildItemValuesFiltered { get; set; }
}
public familyTransfrom(){
TransformResults = parents => from parent in parents
let filterMinValue = Convert.ToDouble(ParameterOrDefault("FilterMinValue", Convert.ToDouble(0)).Value<double>())
let filterMaxValue = Convert.ToDouble(ParameterOrDefault("FilterMaxValue", Convert.ToDouble(9999)).Value<double>())
select new Result{
ParentItemId = parent.ParentItemId,
ParentItemName = parent.ParentItemName,
ParentItemValue = parent.ParentItemValue,
//ChildItemValuesFiltered = parent.ChildItems.Where(p => p.ChildItemValues.Any(y => Convert.ToDouble(y) >= Convert.ToDouble(filterMinValue) && Convert.ToDouble(y) <= Convert.ToDouble(filterMaxValue))).SelectMany(t => t.ChildItemValues).ToArray<double>(),
ChildItemValuesFiltered = parent.ChildItems.SelectMany(p => p.ChildItemValues.Where(y => Convert.ToDouble(y) >= Convert.ToDouble(filterMinValue) && Convert.ToDouble(y) <= Convert.ToDouble(filterMaxValue))).ToArray<double>(),
ChildItems = Recurse(parent, x => x.ChildItems).Select(y => y).ToArray()
};
}
}
public class familyIndex : AbstractIndexCreationTask<ParentItem>{
public class Result : ParentItem {
public double[] ChildItemValues { get; set; }
}
public familyIndex(){
Map = parents => from parent in parents
select new Result{
ParentItemId = parent.ParentItemId,
ParentItemName = parent.ParentItemName,
ParentItemValue = parent.ParentItemValue,
ChildItemValues = parent.ChildItems.SelectMany(p => p.ChildItemValues.Select(y => y)).ToArray(),
ChildItems = Recurse(parent, x => x.ChildItems).Select(y => y).ToArray()
};
Index("ParentItemId", FieldIndexing.Analyzed);
Index("ParentItemName", FieldIndexing.Analyzed);
Index("ParentItemValue", FieldIndexing.Analyzed);
Index("ChildItemValues", FieldIndexing.Analyzed);
Index("ChildItems", FieldIndexing.Analyzed);
}
}
my query is as follows , (this is using the live raven playground so this should just work out of the box it you want to use it)
using (IDocumentStore store = new DocumentStore { Url = "http://live-test.ravendb.net/", DefaultDatabase = "altha" })
{
store.Initialize();
using (IDocumentSession session = store.OpenSession())
{
if(1 == 2){
//foreach (ParentItem element in data.OfType<ParentItem>()) {
// session.Store((ParentItem)element);
// session.SaveChanges();
//}
new familyIndex().Execute(store);
new familyTransfrom().Execute(store);
}else{
double filterMinValue = 3.0;
double filterMaxValue = 4.0;
var results = session
.Advanced
.DocumentQuery<familyIndex.Result,familyIndex>()
.WhereBetweenOrEqual("ChildItemValues", filterMinValue, filterMaxValue)
.SetResultTransformer<familyTransfrom, familyTransfrom.Result>()
.SetTransformerParameters(new Dictionary<string, RavenJToken> {
{ "FilterMinValue", filterMinValue },
{ "FilterMaxValue", filterMaxValue } })
.OrderBy("ChildItemValues")
.OfType<ParentItem>().ToList();
results.Dump();
}}
}
What i found was i cant use "ChildItemValuesFiltered" from the transform result as its not index. So unless i can order by the result of a transform ? i couldn't get this to work as although it filters it dosnt order correctly.
Is there another to achieve what i want using projections or intersection or rank or reduce try method ?
I was thinking if i had to perhaps i could use https://ravendb.net/docs/article-page/3.5/csharp/indexes/querying/sorting#custom-sorting
and do something like this:
public class SortByNumberOfCharactersFromEnd : IndexEntriesToComparablesGenerator
{
private readonly double filterMinValue;
private readonly double filterMinValue;
public SortByNumberOfCharactersFromEnd(IndexQuery indexQuery)
: base(indexQuery)
{
filterMinValue = IndexQuery.TransformerParameters["FilterMinValue"].Value<double>(); // using transformer parameters to pass the length explicitly
filterMaxValue = IndexQuery.TransformerParameters["FilterMaxValue"].Value<double>();
}
public override IComparable Generate(IndexReader reader, int doc)
{
var document = reader.Document(doc);
double[] childItemValues = (double[])document.GetValues("ChildItemValuesFiltered").Select(double.Parse).ToArray(); // this field is stored in index
return childItemValues.Where(x => x >= min && x <= max).Min();
}
}
then do a where filter and order by clause using index and transform passing in the same prams that i use in the where filter . however im not sure if this would work ?
More importantly im not sure how i go about getting the sort dll into the plugins ie what name space should the class go under, what name spaces dose it need to import, what assembly name dose it need to use etc
According to https://ravendb.net/docs/article-page/3.5/csharp/server/plugins/what-are-plugins i just need to drop the dll in and raven will this this up , however i cant seem to find what name space i need to reference for IndexEntriesToComparablesGenerator ?
im using linqpad 5 to test my stuff ...so in order to use the custom order i have to reference the class
any tips or advice or how to guild/examples welcome
so it didn't occur to me that i could do the filtering in the transform
TransformResults = parents => from parent in parents
let filterMinValue = Convert.ToDouble(ParameterOrDefault("FilterMinValue", Convert.ToDouble(0)).Value<double>())
let filterMaxValue = Convert.ToDouble(ParameterOrDefault("FilterMaxValue", Convert.ToDouble(9999)).Value<double>())
select new {
ParentItemId = parent.ParentItemId,
ParentItemName = parent.ParentItemName,
ParentItemValue = parent.ParentItemValue,
//ChildItemValuesFiltered = parent.ChildItems.Where(p => p.ChildItemValues.Any(y => Convert.ToDouble(y) >= Convert.ToDouble(filterMinValue) && Convert.ToDouble(y) <= Convert.ToDouble(filterMaxValue))).SelectMany(t => t.ChildItemValues).ToArray<double>(),
ChildItemValuesFiltered = parent.ChildItems.SelectMany(p => p.ChildItemValues.Where(y => Convert.ToDouble(y) >= Convert.ToDouble(filterMinValue) && Convert.ToDouble(y) <= Convert.ToDouble(filterMaxValue))).ToArray<double>(),
ChildItems = Recurse(parent, x => x.ChildItems).Select(y => y).ToArray()
} into r
where r.ChildItemValuesFiltered.Length > 0
orderby r.ChildItemValuesFiltered.Min()
select r;
This gives me what i wanted, here are the sample query:
http://live-test.ravendb.net/databases/altha/indexes/familyIndex?start=0&pageSize=25&resultsTransformer=familyTransfrom&tp-FilterMinValue=3&tp-FilterMaxValue=4
i cant take credit for this as guys at raven helped me but sharing the knowledge for others

RavenDB: How do I fix this Map Reduce code? I'm getting NULL values where I'm expecting Results

I have the following code:
public class WorkOrderByUserId : AbstractMultiMapIndexCreationTask<WorkOrderByUserId.Result>
{
public WorkOrderByUserId()
{
this.AddMap<WorkOrder>(items
=> from item in items
select new Result
{
OwnerId = item.OwnerId,
WorkOrder = new WorkOrderLookupDto
{
Id = item.Id,
Name = item.EventName
},
WorkOrders = null
});
this.AddMap<Invoice>(items
=> from item in items
select new Result
{
OwnerId = item.WorkOrder.OwnerId,
WorkOrder = new WorkOrderLookupDto
{
Id = item.WorkOrder.Id,
Name = item.WorkOrder.EventName
},
WorkOrders = null
});
this.Reduce = results => from result in results
group result by result.OwnerId
into g
select new Result
{
OwnerId = g.Key,
WorkOrders = g.Select(x => x.WorkOrder),
WorkOrder = null
};
this.Indexes.Add(x => x.OwnerId, FieldIndexing.Default);
}
public class Result
{
public string OwnerId { get; set; }
public IEnumerable<WorkOrderLookupDto> WorkOrders { get; set; }
public WorkOrderLookupDto WorkOrder { get; set; }
}
}
It gets me very close to where I want to be, however I seem to be missing something and I'm losing information and I'm not sure why.
Using the Map/Reduce Visualizer I notice the MAP is displaying the Results (i.e. WorkOrders is populated, and WorkOrder is null)
As this point I was expecting to have a bunch of items with NULL WorkOrders and a valid WorkOrder, which I expected to reduce down into the WorkOrders collection.
When I look at the final reduce in the visualizer, I notice my WorkOrder is NULL and my WorkOrders are missing entirely.
And when I look at the FINAL result of the Index I see what I'm looking for, just without the actual data I'm looking for.
What do I need to change to get my final WorkOrders to NOT be NULL?
I was able to get my desired result using the following code:
public class WorkOrderByUserId : AbstractMultiMapIndexCreationTask<WorkOrderByUserId.Result>
{
public WorkOrderByUserId()
{
this.AddMap<WorkOrder>(items
=> from item in items
select new Result
{
OwnerId = item.OwnerId,
WorkOrders = new[]
{
new WorkOrderLookupDto
{
Id = item.Id,
Name = item.EventName
}
}
});
this.AddMap<Invoice>(items
=> from item in items
select new Result
{
OwnerId = item.WorkOrder.OwnerId,
WorkOrders = new[]
{
new WorkOrderLookupDto
{
Id = item.WorkOrder.Id,
Name = item.WorkOrder.EventName
}
}
});
this.Reduce = results => from result in results
group result by result.OwnerId
into g
select new Result
{
OwnerId = g.Key,
WorkOrders = g.SelectMany(x => x.WorkOrders)
};
this.Indexes.Add(x => x.OwnerId, FieldIndexing.Default);
}
public class Result
{
public string OwnerId { get; set; }
public IEnumerable<WorkOrderLookupDto> WorkOrders { get; set; }
}
}

entity framework orderby sum slow performance

I have a repository table and add any transaction of products here like this:
productID qty:
103 2
103 -1
in the view of my products I want to show products order by sum of the qty of products > 0
so I write this :
dbContext.tbl_Product.OrderByDescending(n => n.tbl_repository.Sum(x => x.Qty) > 0).ThenByDescending(m => m.ID);
but the performance of this is too slow, is there any other way to make it speedy ?
You can try leveraging the GroupBy method.
First create an (optional) class to hold the results:
public class ProductGroup
{
public string ProductID { get; set; } // or int, whatever your product id's type is
public int QuantitySum { get; set; }
}
I'm not sure how your repository is implemented, but try querying the context directly first and see if you get better results:
using (var db = new ApplicationContext())
{
var products = db.Products
.GroupBy(product => product.ProductID)
.Where(productGroup => productGroup.Sum(p => p.Quantity) > 0)
.Select(productGroup => new ProductGroup { ProductID = productGroup.Key, QuantitySum = productGroup.Sum(product => product.Quantity) })
.OrderByDescending(product => product.ProductID)
.ToList();
// 'products' should be a list of ProductGroup items holding the ID and Sum.
}

nhibernate: select parents and newest child

I have the following entities
public class Vehicle
{
public virtual string InternalNumber { get; set; }
public virtual IList<VehicleDriverHistory> DriverHistory { get; set; }
}
public class VehicleDriverHistory : HistoryEntity
{
public virtual Vehicle Vehicle { get; set; }
public virtual DriverPerson Driver { get; set; }
public virtual DateTime Date { get; set; }
}
Now I need to select every Vehicle and, if present, the newest VehicleDriverHistory-Entry according to "Date".
But I'm kinda struggling, I have no problem doing it in sql, but failed in nhibernate so far.
What I have so far:
VehicleDriverHistory vdHistory = null;
VehicleDriverHistory vdHistory2 = null;
q.Left.JoinAlias(x => x.DriverHistory, () => vdHistory);
q.Left.JoinAlias(x => x.DriverHistory, () => vdHistory2).Where(x => vdHistory2.Date > vdHistory.Date);
q.Where(x => vdHistory2.Id == null);
This doesn't work and it was just my attempt to "translate" the sql query (which yields the correct data) to nhibernate.
So, how would I select parents and the newest child (or none if none is present)
Thanks
UPDATE
With Firos help I ended up with the following:
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = DataSession.Current.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Future();
Vehicle VehicleAlias = null;
var vehiclesWithoutEntry = DataSession.Current.QueryOver<Vehicle>(() => VehicleAlias)
.WithSubquery
.WhereNotExists(QueryOver.Of<VehicleDriverHistory>()
.Where(x => x.Vehicle.Id == VehicleAlias.Id).Select(x => x.Id))
.Future();
return vehiclesWithEntry.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h }).Concat(vehiclesWithoutEntry.Select(v => new PairDTO { Vehicle = v })).ToList();
I had to replace the Any() statement in vehclesWithoutEntry with a subquery-clause because it raised an exception.
some other idea using Futures to make only one roundtrip
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = session.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h })
.Future<PairDTO>();
var vehiclesWithoutEntry = session.QueryOver<Vehicle>()
.Where(v => !v.DriverHistory.Any())
.Select(v => new PairDTO{ Vehicle = v })
.Future<PairDTO>();
return vehiclesWithoutEntry.Concat(vehiclesWithEntry); // .ToList() if immediate executing is required
Update: i can not reproduce the exception but you could try this
VehicleDriverHistory historyAlias = null;
var maxDateQuery = QueryOver.Of<VehicleDriverHistory>()
.Where(h => h.Vehicle == historyAlias.Vehicle)
.Select(Projections.Max<VehicleDriverHistory>(h => h.Date));
var vehiclesWithEntry = session.QueryOver(() => historyAlias)
.WithSubquery.WhereProperty(h => h.Date).Eq(maxDateQuery)
.Fetch(h => h.Vehicle).Eager
.Future();
var vehiclesWithoutEntry = session.QueryOver<Vehicle>()
.Where(v => !v.DriverHistory.Any())
.Select(v => new PairDTO{ Vehicle = v })
.Future<PairDTO>();
return vehiclesWithoutEntry.Select(h => new PairDTO { Vehicle = h.Vehicle, NewestHistoryEntry = h }).Concat(vehiclesWithEntry);

How use a select in simple QueryOver

I have a query by QueryOver In Nhibernate 3.1
var q = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code);
.Select(p => p.Name,p => p.Code);
return q.SingleOrDefault();
This query without select is correct, but with select has a runtime error by this message: Unable to cast object of type 'System.String' to type 'MyNameSpace.Domain.Entities.Person'. How can i select some field of Person?
you need to tell NHibernate explicitly what is the type of your return data since you choose to select some specific properties of your original entity p.Name,P.Code these properties must by returned as of a new type of entity so the new type would be a new object
var query = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code)
.Select(
p => p.Name,
p => p.Code
)
.List<object>();
this will solve your problem
or if you defined a new entity to hold the new return data as following :
public newPeople
{
public Id { get; set; }
public Nam { get; set; }
}
you can write it like that:
var query = SessionInstance.QueryOver<Person>()
.Where(p => p.Code == code)
.Select(
p => p.Name,
p => p.Code
)
.Select( list =>
new newPeople()
{
Id = (int) list[0],
Name = (string) list[1]
})
.ToList<newPeople>();