Performance Issue with NHibernate Query - performance

I am currently having a performance problem with the following query written in NHibernate. I am trying to transform the data I queried into DTO's. With this complex structure I cannot use QueryOver to transform the entities. On the other side Linq provider is so useful but it takes ~10 seconds to load and transform ~6000 entities with each 30 child items. It creates an SQL query with left outer join. Are there any other ways to write this query with a better approach?
var Entities = session.Query<crmEntity>()
.Where(x => x.EntityType.ID == EntityType)
.Select(entity => new EntityDTO()
{
ID = entity.ID,
EntityType = entity.EntityType.ID,
InstanceID = entity.Instance.ID,
Values = entity.Values.Select(
value => new CustomFieldValueDTO()
{
ID = value.ID,
FieldID = value.Field.ID,
Value = value.Value
}).ToList<CustomFieldValueDTO>()
}).ToList();

Here is my solution. if there is any other better way, I am completely open to it:
session.CreateQuery(#"select vals.ID,
vals.Field.ID,
vals.Value,
ent.ID
from crmEntity ent inner join ent.Values vals
with vals.Value IS NOT NULL
where ent.EntityType.ID=:eID and ent.Instance.ID=:instanceID order by ent.ID")
.SetGuid("instanceID", InstanceID)
.SetGuid("eID", EntityType)
.SetResultTransformer(new EntityListTransformer()).Future<ReadOnlyEntityDTO>();
And this is my custom result transformer to get the same hierarchy like my linq query
public class EntityListTransformer : IResultTransformer
{
private List<ReadOnlyEntityDTO> list;
private ReadOnlyEntityDTO lastEntity;
private Guid instanceID;
public EntityListTransformer()
{
list = new List<ReadOnlyEntityDTO>();
lastEntity = new ReadOnlyEntityDTO();
}
public System.Collections.IList TransformList(System.Collections.IList collection)
{
return list;
}
public object TransformTuple(object[] tuple, string[] aliases)
{
string ValueID = tuple[0].ToString();
string FieldID = tuple[1].ToString();
string Value = (string)tuple[2];
string EntityID = tuple[3].ToString();
if (lastEntity.ID != EntityID)
{
if (lastEntity.ID != null)
{
list.Add(lastEntity);
}
lastEntity = new ReadOnlyEntityDTO()
{
ID = EntityID
};
}
lastEntity.Values.Add(new ReadOnlyCustomFieldValueDTO()
{
FieldID = FieldID,
ID = ValueID,
Value = Value
});
return tuple;
}
}

Related

Linq Where with variable header

I´m trying to do something like this:
public class Person{string name;string surname;}
//...
List<Person> listExample;
//We add Person object in listExample
string variable="name";
listexample.Where(x=>x.(variable)=="John");
Is it posible to do something similar ?
If you need to access non-public fields then you can use reflection:
class Person
{
public Person(string name)
{
this.name = name;
}
string name;
}
List<Person> people = new List<Person>()
{
new Person("Jane"),
new Person("John")
};
string variableName = "name";
string criteria = "John";
var selectedPeople =
people
.Where(person =>
typeof(Person)
.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
.Where(fieldInfo => String.Equals(fieldInfo.Name, variableName))
.Select(fieldInfo => fieldInfo.GetValue(person) as string)
.SingleOrDefault() == criteria
)
.ToList();
// At this point 'selectedPeople' will contain one 'Person' named "John"
If you need to access properties then you can use GetProperties(BindingFlags bindingAttr) method instead of GetFields(...) method.
class Person
{
public Person(string name)
{
this.Name = name;
}
public string Name { get; set; }
}
// in a method
var selectedPeople =
people
.Where(person =>
typeof(Person)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(propertyInfo => String.Equals(propertyInfo.Name, variableName))
.Select(propertyInfo => propertyInfo.GetValue(person) as string)
.SingleOrDefault() == criteria
)
.ToList();
You should take a look at BindingFlags enum so that you can select the appropriate enum values.
BindingFlags.Static for static members
BindingFlags.Instance for instance members
BindingFlags.Public for public members
BindingFlags.NonPublic for private / protected members
etc.
The easiest way, considering the class has only two possible fields to filter on, would be to use simple if-else block :
string targetProperty = "name";
string targetValue = "John";
IEnumerable<Person> query = listExample;
if(targetProperty == "name") query = query.Where(x => x.name == targetValue)
else if(targetProperty == "surname") query = query.Where(x => x.surname == targetValue)
var result = query.ToList();
If the actual class has a lot of possible properties/fields to filter on, and you want to avoid too many if-branches, it's time to use Expression as mentioned in the comment below the question :
string targetProperty = "name";
string targetValue = "John";
var param = Expression.Parameter(typeof(Person), "x");
var body = Expression.Equal(
Expression.PropertyOrField(param, targetProperty),
Expression.Constant(targetValue));
var predicate = Expression.Lambda<Func<Person, bool>>(body, param);
var result = listExample.Where(predicate.Compile()).ToList();
The above codes should build predicate expression equivalent to x => x.name == "John" (since targetProperty = "name" and targetValue = "John")
There's a nuget package for this that lets you write;
using System.Linq.Dynamic; //Import the Dynamic LINQ library
//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
.Where(x => x.Field1 == "SomeValue")
.Select(x => new { x.Field1, x.Field2 });
//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
.Where("Field1=\"SomeValue\"")
.Select("new (Field1, Field2)");

Problem getting same result from DataContext.Translate

This question is a follow up to this question:
How to create a list from two values
Consider this code:
class MainClass()
{
string MainKey {get;set;}
string MainName {get;set;}
IEnumerable<SmallObject> MainList {get;set}
}
class SmallObject()
{
string SmallKey {get;set}
}
and:
var mainQuery = (from v from DataContext.myTable
select v);
var myQuery = (from v in mainQuery
select new MainClass()
{
MainKey = v.Field1,
MainName = v.Field2,
MainList = new []
{
new SmallObject { SmallKey = v.Field3 },
new SmallObject { SmallKey = v.Field4 },
}
});
var result1 = myQuery.ToList();
//Changing datatypes for optimization reasons in SQLServer2000
var cmd = DataContext.GetCommand(myQuery);
foreach (System.Data.Common.DbParameter param in cmd.Parameters)
{
// nvarchar -> varchar
// decimal -> numeric
}
var result2 = DataContext.Translate<MainClass>(cmd.ExecuteReader()).ToList();
result1.MainList is OK
result2.MainList is null
The original query was very slow running on SQLServer2000, and I got it fixed when changing datatypes (Linq uses nvarchar and decimal, as my database use varchar and numeric)
So I want result2 to be the same as result1, but that doesn't happen when doing a DataContext.Translate like this.
Any thoughts of getting the same result here?
I've also tryed anonymous types, like this:
IEnumerable<object> MainList {get;set;}
...
MainList = new []
{
new { SmallKey = v.Field3},
new { SmallKey = v.Field4},
}
but the result is the same:
I think you are asking too much from Translate.
If I understand you correctly, it is the first query (mainQuery) that is too slow, so I would look to replace it.
I would create a simpler temporary class like
public class TmpClass
{
public string Field1 {get;set;}
public string Field2 {get;set;}
public string Field3 {get;set;}
public string Field4 {get;set;}
}
Once the list is in this format, you can use the second query to change it to a list of MainClass.
Just a matter of interest, what is the difference between the sql outputted by Linq and your customized version? Unless it is does some casting, I would not expect this type of query to need optimizing.
I would use the AsEnumerable extension method which basically converts the IQueryable to an IEnumerable which forces the enumerator to be processed. You could achieve the same thing by calling ToArray() or ToList() but AsEnumerable() magically lets you return it back to an IQueryable by calling AsQueryable()
So probably doing the following will work for you:
var result1 = DataContext.myTable.AsEnumerable()
.Select(v=> new MainClass {
MainKey = v.Field1,
MainName = v.Field2,
MainList = new []
{
new SmallObject { SmallKey = v.Field3 },
new SmallObject { SmallKey = v.Field4 },
}
});

How does DynamicQueryable support the Array operator?

I have been using the DynamicQueryable Linq extensions featured in Scott Guthrie's blog post.
The documentation has a table of supported operators. One of the primary operators is the following:
x[…]
Array or indexer access. Multi-dimensional arrays are not supported.
However, I cannot figure out how it can be used.
I didn't expect any of the following to work and in fact they don't.
var ctx = new MyDbContext();
var parameters = new Object[] { new int[] { 1, 2, 3 } };
var qry = ctx.Set<User>().Where<User>("it.Id in #0", parameters);
var qry = ctx.Set<User>().Where<User>("it.Id.In(#0)", parameters);
var qry = ctx.Set<User>().Where<User>("it.Id = #0", parameters);
var qry = ctx.Set<User>().Where<User>("#0.Contains(it.Id)", parameters);
It is basically an In query, but I am not sure how to express it.
This is perhaps a misunderstanding. Meant is that it is possible to query for collection elements at a specific index position. For example:
public class Order
{
public List<OrderDetail> OrderDetails { get; set; }
}
public class OrderDetail
{
public string Description { get; set; }
}
Then you can query for all orders which have the Detail description "Bicycle" in the third OrderDetail by:
string parameter = "Bicycle";
var qry = ctx.Set<Order>().Where<Order>("it.OrderDetails[2].Description == #0",
parameter);
I think for your purpose you need to build up an "OR" chain "it.Id == 1 or it.Id == 2 or it.Id == 3" (or build this query string dynamically in a loop) without parameters in the Where method.

How to dynamically choose two fields from a Linq query as a result

If you have a simple Linq query like:
var result = from record in db.Customer
select new { Text = record.Name,
Value = record.ID.ToString() };
which is returning an object that can be mapped to a Drop Down List, is it possible to dynamically specify which fields map to Text and Value?
Of course, you could do a big case (switch) statement, then code each Linq query separately but this isn't very elegant. What would be nice would be something like:
(pseudo code)
var myTextField = db.Customer["Name"]; // Could be an enumeration??
var myValueField = db.Customer["ID"]; // Idea: choose the field outside the query
var result = from record in db.Customer
select new { Text = myTextField,
Value = myValueField };
Right way to do this is with closures.
Func<Customer, string> myTextField = (Customer c) => c["Name"];
Func<Customer, int> myValueField = (Customer c) => c["ID"];
var result = from record in db.Customer
select new { Text = myTextField(record),
Value = myValueField(record) };
The one limitation is that your definition of myTextField always needs to return a string.
You could try something like
class Customer
{
public int ID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
}
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.ID.ToString() },
{ "Name", (Customer c) => c.Name},
{ "Surname", (Customer c) => c.Surname } };
List<Customer> rows = new List<Customer>();
rows.Add(new Customer { ID = 1, Name = "Foo", Surname = "Bar"});
var list = from r in rows
select new { Text = dict["ID"](r), Value = dict["Name"](r) };
To try to access the properties dynamically, you could try something like
var dict = new Dictionary<string, Func<Customer, string>>
{ { "ID", (Customer c) => c.GetType().GetProperty("ID").GetValue(c,null).ToString() },
{ "Name", (Customer c) => c.GetType().GetProperty("Name").GetValue(c,null).ToString()},
{ "Surname", (Customer c) => c.GetType().GetProperty("Surname").GetValue(c,null).ToString() } };

Dynamic Order (SQL ORDERBY) in LINQ CompiledQuery

how can I create a dynamic ORDERBY in my LINQ CompiledQuery (e.g. supply Order Field and Direction as parameters for the compiled query)?
I would do it this way, first all you really need is a way to access the property value by string on an object. You could use reflection, but its slow. So use this helper class approach which is based on the tests of http://stefan.rusek.org/Posts/LINQ-Expressions-as-Fast-Reflection-Invoke/3/
public static class LINQHelper
{
public static IComparable OrderByProperty<TClass>(TClass item,
string propertyName)
{
var t = Expression.Parameter(typeof(TClass), "t");
var prop = Expression.Property(t, propertyName);
var exp = Expression.Lambda(prop, t).Compile();
return (IComparable)exp.DynamicInvoke(item);
}
}
The in your code where you want your order by string of property name, in this example col1, you just do the following.
var myQuery = from i in Items
select i;
myQuery.OrderBy(i=>LINQHelper.OrderByProperty(i,"col1"));
Hope this helps.
I think I found it:
Check out this link. It will point you to the VS2008 code samples which contains a Dynamic Linq Query Library which contains the extension method below. This will allow you to go:
Object.OrderBy("ColumnName");
Here is the extension methods, but you may want the whole library.
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values) {
return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
}
public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values) {
if (source == null) throw new ArgumentNullException("source");
if (ordering == null) throw new ArgumentNullException("ordering");
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "") };
ExpressionParser parser = new ExpressionParser(parameters, ordering, values);
IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering();
Expression queryExpr = source.Expression;
string methodAsc = "OrderBy";
string methodDesc = "OrderByDescending";
foreach (DynamicOrdering o in orderings) {
queryExpr = Expression.Call(
typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
new Type[] { source.ElementType, o.Selector.Type },
queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
methodAsc = "ThenBy";
methodDesc = "ThenByDescending";
}
return source.Provider.CreateQuery(queryExpr);
}

Resources