Reputation: 1343
I am trying to build a custom Hql generator which must build a Case construct. This construct is to be used in an order by clause. I am trying to do an alphabetically sort on an enumeration (a Gender enum in this case) in the language of the current user. As you can see, the sortorder is retrieved from the GenderResourceTextAttribute. The values in the order array must be used in the Case construct. This is what I have so far:
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
GenderResourceTextAttribute attribute = null;
if (targetObject.Type.IsEnum)
{
attribute = targetObject.Type.GetCustomAttributes(typeof(GenderResourceTextAttribute), false).FirstOrDefault() as GenderResourceTextAttribute;
}
int[] order = attribute.GetSortOrderPosition();
return treeBuilder.Case(new HqlWhen(....));
}
[GenderResourceText]
public enum Gender
{
Unknown = 0,
Men,
Women
}
I eventually want it to generate something like the following sql:
case Gender when 0 then 1 when 1 then 2 else 0 end
How can I implement this?
Edited: added my solution, based on Gerben's advice:
Thanks Gerben!
With your provided example I was able to get it done:
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
EnumResourceTextAttribute attribute = (EnumResourceTextAttribute)targetObject.Type.GetCustomAttributes(typeof(EnumResourceTextAttribute), false).FirstOrDefault();
IEnumerable<int> sortOrder = attribute.GetSortOrderPositions(arguments[0].ToString() == "Descending" ? System.Data.SqlClient.SortOrder.Descending : SortOrder.Ascending);
List<HqlExpression> parameters = new List<HqlExpression>();
List<HqlWhen> hqlWhenList = new List<HqlWhen>();
for(int index = 0; index < sortOrder.Count(); index++)
{
int position = sortOrder.ElementAt(index);
hqlWhenList.Add(
treeBuilder.When(
treeBuilder.Equality(visitor.Visit(targetObject).AsExpression(), treeBuilder.Constant(index)),
treeBuilder.Constant(position)
)
);
}
HqlCase hqlCase = treeBuilder.Case(hqlWhenList.ToArray());
return hqlCase;
}
Upvotes: 2
Views: 402
Reputation: 581
My BuildHql method looks like this. It uses a CaseBuilder which is included below.
public override HqlTreeNode BuildHql(
MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor
)
{
// Get the CaseBuilder form the arguments.
var caseBuilder = (arguments[1] as ConstantExpression).Value as CaseBuilder;
var hqlWhenList = new List<HqlWhen>();
// Add a HqlWhen for each CaseBuilderOption.
foreach (var option in caseBuilder.Options)
{
// add the HqlWhen
hqlWhenList.Add(
// create HqlWhen with given treeBuilder.
treeBuilder.When(
// compare given property with the When of the CaseBuilderOption.
treeBuilder.Equality(visitor.Visit(arguments[0]).AsExpression(), treeBuilder.Constant(option.When)),
// add the Then value of the CaseBuilderOption
treeBuilder.Constant(option.Then)
)
);
}
//
return
// cast the returned value to returntype of CaseBuilder.
treeBuilder.Cast(
// create the HqlCase with the TreeBuilder.
treeBuilder.Case(
// add the created HqlWhen list.
hqlWhenList.ToArray(),
// add the final or else value from the CaseBuilder.
treeBuilder.Constant(caseBuilder.ElseValue)
),
// the return type for the cast.
caseBuilder.ReturnType
);
}
The CaseBuilder and the CaseBuilderOption classes.
public class CaseBuilder
{
/// <summary>
/// The options of this case.
/// </summary>
public List<CaseBuilderOption> Options { get; set; }
/// <summary>
/// Else return value.
/// </summary>
public object ElseValue { get; set; }
/// <summary>
/// Type of return value.
/// </summary>
public Type ReturnType { get; set; }
/// <summary>
///
/// </summary>
/// <param name="returnType"></param>
/// <param name="case1"></param>
/// <param name="value1"></param>
/// <param name="elseValue"></param>
public CaseBuilder(Type returnType, object when, object then, object elseValue)
{
ReturnType = returnType;
if (then.GetType() != returnType || elseValue.GetType() != returnType)
{
throw new Exception();
}
Options = new List<CaseBuilderOption>();
Options.Add(new CaseBuilderOption() { When = when, Then = then });
ElseValue = elseValue;
}
/// <summary>
/// Add a WhenThen option to the case builder.
/// </summary>
/// <param name="when"></param>
/// <param name="then"></param>
/// <returns></returns>
public CaseBuilder Append(object when, object then)
{
if (then.GetType() != ReturnType)
{
throw new Exception();
}
Options.Add(new CaseBuilderOption() { When = when, Then = then });
return this;
}
}
/// <summary>
/// A When Then option of a Case
/// </summary>
public class CaseBuilderOption
{
/// <summary>
/// When
/// </summary>
public object When { get; set; }
/// <summary>
/// returns this value if When and Case property are equal
/// </summary>
public object Then { get; set; }
}
I hope this will help you.
Upvotes: 2