Need help optimizing a NHibernate criteria query that uses Restrictions.In(..) - nhibernate

I'm trying to figure out if there's a way I can do the following strictly using Criteria and DetachedCriteria via a subquery or some other way that is more optimal. NameGuidDto is nothing more than a lightweight object that has string and Guid properties.
public IList<NameGuidDto> GetByManager(Employee manager)
{
// First, grab all of the Customers where the employee is a backup manager.
// Access customers that are primarily managed via manager.ManagedCustomers.
// I need this list to pass to Restrictions.In(..) below, but can I do it better?
Guid[] customerIds = new Guid[manager.BackedCustomers.Count];
int count = 0;
foreach (Customer customer in manager.BackedCustomers)
{
customerIds[count++] = customer.Id;
}
ICriteria criteria = Session.CreateCriteria(typeof(Customer))
.Add(Restrictions.Disjunction()
.Add(Restrictions.Eq("Manager", manager))
.Add(Restrictions.In("Id", customerIds)))
.SetProjection(Projections.ProjectionList()
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Id"), "Guid"))
// Transform results to NameGuidDto
criteria.SetResultTransformer(Transformers.AliasToBean(typeof(NameGuidDto)));
return criteria.List<NameGuidDto>();
}

return Session.CreateCriteria<Customer>()
.CreateAlias("BackupManagers", "bm", LeftOuterJoin)
.Add(Restrictions.Disjunction()
.Add(Restrictions.Eq("Manager", manager))
.Add(Restrictions.Eq("bm.Id", manager.Id)))
.SetProjection(Projections.Distinct(Projections.ProjectionList()
.Add(Projections.Property("Name"), "Name")
.Add(Projections.Property("Id"), "Guid")))
.SetResultTransformer(Transformers.AliasToBean(typeof(NameGuidDto)))
.List<NameGuidDto>();
I threw a distinct in there because I'm not sure if it is possible to have backup and primary be the same person.

Related

Getting Custom Column from IQueryable DB First Approach EF

I am working on Database First Approach in Entity Framework where I have to retrieve specific columns from the Entity.
Public IQueryable<Entity.Employees> GetEmployeeName(String FName,String LName)
{
var query = (from s in Employees
where s.firstName = FName && s.lastName = LName
select new {s.firstName, s.middleName});
return query;
}
Here return statement is throwing an error where it seems that its not matching with Employees (entity) columns. Could you please help me in sorting out this issue? Thanks in advance.
You need to use == for comparison, also you need to use dynamic type as return type since you are returning a custom anonymous type. Try this
Public IQueryable<dynamic> GetEmployeeName(String FName,String LName)
{
var query=(from s in Employees
where s.firstName==FName && s.lastName==LName
select new {s.firstName,s.middleName});
return query.AsQueryable();
}
Finally you will use it like below, keep in mind that intelisense won't work on dynamic object.
var query = GetEmployeeName("Jake", "Smith");
List<dynamic> results = query.ToList();
foreach (dynamic result in results)
{
string fristName = result.FirstName;
string lastName = result.MiddleName;
}

Entity Framework 4.1 Eager Loading on complext object

In a current MVC4.0 project I am using Entity Framework 4.1 Database first model.
Part of this structure includes the following tables
compGroupData
SurveyData
SecondaryData
compGroupData and SurveyData are not joined in the database
SecondaryData is joined to SurveyData on a one to one relationship via a Foreign Key SurveyData.surveydatakey = SecondaryData.surveydatakey
In my project I have a class ComparisonWithData defined as:
public class ComparisonWithData
{
public compGroupData compgrp { get; set; }
public SurveyData surveydata { get; set; }
public ComparisonWithData()
{
compgrp = new compGroupData();
surveydata = new SurveyData();
}
}
This gives me a result set for a specific Comparison group and the data that matches this.
In the past I have retrieved the data for this via the following query:
List<ComparisonWithData> comparisonwithdata = ((from compgrp in db.compGroupDatas
where compgrp.grpYear == rptyear && compgrp.CompGroupID == ccompgrp.CompGrpID
join surveydata in db.SurveyDatas on new { compgrp.companyid, SurveyYear = (Int32)compgrp.SurveyYear } equals new { companyid = surveydata.companyid, SurveyYear = surveydata.surveyyear }
select new ComparisonWithData
{
compgrp = compgrp,
surveydata = surveydata,
}
)).ToList();
With a recent change in data I now need to also reference the SecondaryData but due to the number of records really need this to load Eagerly instead of Lazy. (Lazy loading during the loop results in thousands of DB calls)
I have looked at using the "Include" method on surveydata as well as casting the initial query as an ObjectQuery and doing the Include off that.
The first method doesn't eager load and the second method seems to always return a null object as a result.
Is there a method to Eager load the SecondaryData for SurveyData or should I be looking at a different approach all together.
My only restriction on this is that I can't go up to EF5 because of a limitation we have on .Net 4.5
Any assistance would be greatly appreciated.
Thank you.
You could try to project into an anonymous object first and use also SecondaryData in that projection, materialize this result and then project again into your final result object. Automatic Relationship Fixup that the EF context provides should populate the navigation property surveyData.SecondaryData of your ComparisonWithData object (as long as you don't disable change tracking in your query):
var data = (( // ... part up to select unchanged ...
select new // anonymous object
{
compgrp = compgrp,
surveydata = surveydata,
secondarydata = surveydata.SecondaryData
}
)).AsEnumerable();
// part until here is DB query, the rest from here is query in memory
List<ComparisonWithData> comparisonwithdata =
(from d in data
select new ComparisonWithData
{
compgrp = d.compgrp,
surveydata = d.surveydata
}
)).ToList();

Linq strategy for complex set

Am developing a ViewModel/PresentationModel which is getting complex.
I want the Linq query to return an IQueryable<UserPresentationModel>
Using EntityFramework against MSSQL
Is it possible to do any sort of iteration over the set before returning it to the presentation layer ie
List<UserPresentationModel> list = new List<UserPresentationModel>();
foreach (var person in listOfPeople)
{
UserPresentationModel u = new UserPresentationModel();
int userUIStatus = GetColourStateOfPerson(person);
u.FirstName = person.FirstName;
u.UserUIStatus = userUIStatus;
list.Add(u);
}
return list
This feels like it would always be N+1, and I'd never get the advantages of deferred execution, composing of queries..
Or (and I think am answering my own question) do I need to think in a SQL set based manner.
First, we can convert your code to LINQ.
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
}
return models.ToList();
Now, if GetColourStateOfPerson is making a DB round-trip, you definitely want to pull that out.
IDictionary<int, int> colourStatesByPersonId = GetColourStatesOfPeople(listOfPeople);
IEnumerable<UserPresentationModel> models =
from person in listOfPeople
select new UserPresentationModel
{
FirstName = person.FirstName,
UserUIStatus = colourStatesByPersonId[person.PersonId]
}
return models.ToList();
You could probably manage to create a single LINQ query that grabs just the first names and colour states of the people you want in a single query, but you haven't provided enough information about your data context for me to help you with that.
I would personally avoid passing around an IQueryable, which could continue making database trips any time somebody touches it. Let your data layer get out all the data you're likely to need, compose it into a list, and return that.
use IEnumerable<T>.Aggregate() instead of looping.
return listOfPeople.Aggregate(new List<UserPresentationModel>(), person => {
return new UserPresentationModel {
FirstName = person.FirstName,
UserUIStatus = GetColourStateOfPerson(person)
};
}).AsQueryable();
return listOfPeople.AsEnumerable().Select(p =>
new UserPresentationModel
{
FirstName = p.FirstName,
UserUIStatus = GetColourStateOfPerson(p)
}).AsQueryable();
I'm assuming that listOfPeople is an IQueryable that will eventually execute against your database. If that is the case then AsEnumerable() is important because SQL Server won't know what to do with GetColourStateOfPerson(). AsEnumerable() will force the IQueryable's expression tree to execute, pull the resulting rows out of your database and then apply Select() transformation in code as oppose to in SQL Server.
If you can implement GetColourStateOfPerson() as a stored proc or database function then you can omit AsEnumerable() and AsQueryable() and allow execution to delay even longer.

Adding a index to collection using EF 4.1 and XAML (For a highscore table)

I have a webservice I call from a WP7 app. I get a list of high scores in a table (name/score).. What is the simpliest way to add a 3rd column on the far left which is simply the row?
Do I need to add a property to the entity? Is there someway to get the row #?
I tried these things below with no success..
[OperationContract]
public List<DMHighScore> GetScores()
{
using (var db = new DMModelContainer())
{
// return db.DMHighScores.ToList();
var collOrderedHighScoreItem = (from o in db.DMHighScores
orderby o.UserScore ascending
select new
{
o.UserName,
o.UserScore
}).Take(20);
var collOrderedHighScoreItem2 = collOrderedHighScoreItem.AsEnumerable().Select((x, i) => new DMHighScoreDTO
{
UserName = x.UserName,
UserScore = x.UserScore
}).ToList();
}
}
[DataContract]
public class DMHighScoreDTO
{
int Rank;
string UserName;
string UserScore;
}
So lets assume you want to load top 100 users in leaderboard and you want to have their rank included:
[OperationContract]
public List<ScoreDto> GetTop100()
{
// Linq to entities query
var query = (from u from context.Users
order by u.Score
select new
{
u.Name,
u.Score
}).Take(100);
// Linq to objects query from working on 100 records loaded from DB
// Select with index doesn't work in linq to entities
var data = query.AsEnumerable().Select((x, i) => new ScoreDto
{
Rank = i + 1,
Name = x.Name,
Score = x.Score
}).ToList();
return data;
}
what will the row number be used for? if this is for ordering might I suggest adding a column named Order, then map the column to your entity.
if you require a row index, you could also call the .ToList() on the query and fetch the index locations for each entity.
Edit:
you could add the Rank property and set it to Ignore. This will enable you to go through the collection set the rank with a simple for loop. This will also not be persisted in the database. It will also not have any required columns in the database.
It does add an extra iteration.
the other way to go about it. This would be to add the rank number in the generated UI and not in the data collection being used to bind.

NHibernate, Sum Query

If i have a simple named query defined, the preforms a count function, on one column:
<query name="Activity.GetAllMiles">
<![CDATA[
select sum(Distance) from Activity
]]>
</query>
How do I get the result of a sum or any query that dont return of one the mapped entities, with NHibernate using Either IQuery or ICriteria?
Here is my attempt (im unable to test it right now), would this work?
public decimal Find(String namedQuery)
{
using (ISession session = NHibernateHelper.OpenSession())
{
IQuery query = session.GetNamedQuery(namedQuery);
return query.UniqueResult<decimal>();
}
}
As an indirect answer to your question, here is how I do it without a named query.
var session = GetSession();
var criteria = session.CreateCriteria(typeof(Order))
.Add(Restrictions.Eq("Product", product))
.SetProjection(Projections.CountDistinct("Price"));
return (int) criteria.UniqueResult();
Sorry! I actually wanted a sum, not a count, which explains alot. Iv edited the post accordingly
This works fine:
var criteria = session.CreateCriteria(typeof(Activity))
.SetProjection(Projections.Sum("Distance"));
return (double)criteria.UniqueResult();
The named query approach still dies, "Errors in named queries: {Activity.GetAllMiles}":
using (ISession session = NHibernateHelper.OpenSession())
{
IQuery query = session.GetNamedQuery("Activity.GetAllMiles");
return query.UniqueResult<double>();
}
I think in your original example, you just need to to query.UniqueResult(); the count will return an integer.