How do I do a String.IsNullOrEmpty() test in an NHibernate Queryover where clause? - nhibernate

I hit a situation today where a field in our legacy db that should never be empty... was empty.
I am using NHibernate 3.2 against this database and the queries that are affected are written in QueryOver.
My current query is this
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName)
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
I want it to be this:
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName &&
!string.IsNullOrEmpty(fg.Code))
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
When I try this I get an exception "Unrecognised method call: System.String:Boolean IsNullOrEmpty(System.String)"
So NHibernate can't translate string.IsNullOrEmpty. Fair enough. However when I try this
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId &&
fg.UserName == Token.UserName &&
!(fg.Code == null || fg.Code.Trim() == "" ))
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);
I get an InvalidOperationException "variable 'fg' of type 'Domain.Entities.FacilityGroup' referenced from scope '', but it is not defined"
Any thoughts?

Ok... I guess I asked this question too soon. I figured out a way around this.
What I was able to do was invoke the 'trim' function from hql via a SQL Function Projection. I ended up writing it as IQueryOver Extention method to keep it flexible. I will post it here in case anyone needs it.
public static class QueriesExtentions
{
public static IQueryOver<E, F> WhereStringIsNotNullOrEmpty<E, F>(this IQueryOver<E, F> query, Expression<Func<E, object>> propExpression)
{
var prop = Projections.Property(propExpression);
var criteria = Restrictions.Or(Restrictions.IsNull(prop), Restrictions.Eq(Projections.SqlFunction("trim", NHibernateUtil.String, prop), ""));
return query.Where(Restrictions.Not(criteria));
}
}
and here it is in use
return Session
.QueryOver<FacilityGroup>()
.Where(fg => fg.Owner.Id == Token.OwnerId && fg.UserName == Token.UserName )
.WhereStringIsNotNullOrEmpty(fg => fg.Code)
.OrderBy(fg => fg.Code).Asc
.TransformUsing(Transformers.DistinctRootEntity);

Related

asp.net mvc Type.GetMethod when two action with same name, but get and post

in asp.net mvc 4, I write a custom Exception handler, working as a Attribute named HandleCustomError.
In this handler, I should know which action the current request for, then I can know what kind of return type for this action. I will return different data for "return type view() and json".
But, now I have two action with the same name, but one is for "Get", other is for "Post".The method "GetMethod" return an error: " System.Reflection.AmbiguousMatchException "
public class HandleCustomError : System.Web.Mvc.HandleErrorAttribute
{
public override void OnException(System.Web.Mvc.ExceptionContext filterContext)
{
//base.OnException(filterContext);
if (filterContext.ExceptionHandled)
{
return;
}
else
{
//Determine the return type of the action
string actionName = filterContext.RouteData.Values["action"].ToString();
Type controllerType = filterContext.Controller.GetType();
var method = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
var returnType = method.ReturnType;
}
....(Omitted)
I also came across this issue that below line was giving Ambigeous issue
controllerType.GetMethod(actionName, BindingFlags.IgnoreCase |
BindingFlags.Public | BindingFlags.Instance);
Reason was that controller has two method with same name one is
[HTTPGET]
and other iis
[HTTPPOST]
.
For to get exact method I use GetMethods and LINQ.
Note: In controller writting [HTTPGet]**on action is not compulsory for get method i.e. on action if HTTpGet is not written the by default .net will consider it as **[HttpGet]. But for post action it is compulsory.
Will use this property for finding the correct method.
Steps
1. Find Type of request
2. Find the action using reflection
Finding Request type:
var actionName =
filterContext.RouteData.Values["action"].ToString();
Type typeOfRequest = filterContext.HttpContext.Request.RequestType.ToLower()
=="get"?typeof(HttpGetAttribute):typeof(HttpPostAttribute);
Finding method:
var cntMethods = controllerType.GetMethods()
.Where(m =>
m.Name == actionName &&
( ( typeOfRequest == typeof(HttpPostAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeOfRequest).Count()>0
)
||
( typeOfRequest == typeof(HttpGetAttribute) &&
m.CustomAttributes.Where(a => a.AttributeType == typeof(HttpPostAttribute)).Count() == 0
)
)
);
MethodInfo actionMethodInfo = actionMethodInfo = cntMethods != null && cntMethods.Count() == 1 ? cntMethods.ElementAt(0):null;
Reference : https://techatfingers.wordpress.com/2016/06/14/session-state-on-action/
As you have mentioned that you have two action with the same name, but one is for "Get", other is for "Post" , You could try out the [HttpGet] to the first action and [HttpPost] to the section action or you can try something like this if your action serves requests from multiple verbs
[HttpGet, HttpPost] or [AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
For Example ,
if your function name is GetMethod then try like this
[HttpGet]
Public ActionResult GetMethod()
{
//do something ...
}
[HttpPost]
Public ActionResult GetMethod()
{
//do something ...
}
One approach to solve this issue is to use the GetMethods() method on the System.Type object to return a list of methods on the System.Type object. Use a LINQ query to filter the method list to just those methods that match a specific action name.
Add this approach by replacing the following in your code:
var method = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
with the following if condition that checks if multiple methods are found that match the variable actionName from your code. Define a string variable that identifies the action type (i.e. "HttpGet" or "HttpPost") and compare it to the attributes of each method in the filtered list.
System.Reflection.MethodInfo[] matchedMethods = controllerType.GetMethods(
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Where(m => m.Name == actionName).ToArray<System.Reflection.MethodInfo>();
if (matchedMethods.Length > 1)
{
string attributeTypeString = "HttpGet"; // Change this to "HttpPut" or the text of any custom attribute filter
foreach (System.Reflection.MethodInfo methodInfo in matchedMethods)
{
if (methodInfo.CustomAttributes.Count() == 0) { continue; }
// An alternative below is to explicitly check against a defined attribute type (e.g. `ca.AttributeType == ...`).
if (methodInfo.CustomAttributes.FirstOrDefault(ca => ca.ToString().IndexOf(attributeTypeString) == 0) !=null)
{
method = methodInfo;
break; // Break out of the 'foreach' loop since a match was found
}
}
}
else
{
method = controllerType.GetMethod(actionName, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
}
Code notes
I used strongly-typed variable declarations (versus 'var') so it's easier to follow what the code is doing.
Add using System.Reflection; and using System.Linq at the top of the class file. I listed the full assembly declaration of a variable/type/flag in the code above so that again it's easier to follow (at least for me) in this answer.
The first line of code in the above solution can be broken into separate lines to make it easier to follow:
System.Reflection.MethodInfo[] allMethods = controllerType.GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
System.Reflection.MethodInfo[] matchedMethods = allMethods.Where(m => m.Name == actionName).ToArray<System.Reflection.MethodInfo>();
Note the if condition within the foreach loop.
if (methodInfo.CustomAttributes.FirstOrDefault(ca => ca.ToString().IndexOf(attributeTypeString) == 0) !=null)
The if condition doesn't have to convert the custom attribute to a string. An alternative is to explicitly check against a defined attribute type (e.g. ca.AttributeType == ...). If the condition does check against the text of an attribute name then ensure the string comparison method (e.g. IndexOf() used here) satisfies the conditions of your application. For example, I used IndexOf() > -1 to determine if the text of variable attributeTypeString is listed anywhere in a custom attribute because I assumed that there should not be another custom attribute that contains the text 'HttpGet'.

NHibernate check for same join in QueryOver

Using QueryOver I am creating a query like this
BulkActionItem bulkActionItemAlias1 = null;
BulkActionItem bulkActionItemAlias2 = null;
var query = GetSession().QueryOver<Student>(() => studentAlias)
.JoinAlias(() => studentAlias.BulkNotifications, () => bulkActionItemAlias1, NHibernate.SqlCommand.JoinType.LeftOuterJoin);
if (query.UnderlyingCriteria.GetCriteriaByAlias("bulkActionItemAlias2") == null
query = query.JoinAlias(() => studentAlias.BulkNotifications, () => bulkActionItemAlias2, NHibernate.SqlCommand.JoinType.LeftOuterJoin);
This will crash because I have the same join twice with different aliases. Is it possible to check if a join already exists on a query, even with a different alias?
I haven't found a built-in way to accomplish this. Typically I use an out parameter with extension methods to keep track of what tables are part of the query. For example:
bool joinedOnBulkNotifications;
BulkNotification notificationAlias = null;
Expression<Func<object>> aliasExpr = () => notificationAlias;
var query = GetSession().QueryOver<Student>(() => studentAlias)
.FilterByBulkNotificationStatus(
someCondition, aliasExpr, out joinedOnBulkNotifications);
public static class QueryExtensions
{
public static IQueryOver<Student, Student> FilterByBulkNotificationStatus(
this IQueryOver<Student, Student> query,
bool someCondition,
Expression<Func<object>> aliasExpr,
out bool joinedOnBulkNotifications)
{
joinedOnBulkNotifications = false;
if (someCondition)
{
joinedOnBulkNotifications = true;
query.JoinAlias(s => s.BulkNotifications, aliasExpr);
}
return query;
}
}
The issue is that you might need to reuse the alias you created later. You might be tempted to pass in a BulkNotification and use that, but this only works if the parameter name matches the name of the variable you pass to the extension method. NHibernate uses the name of the variable to create an alias name, so if these two do not match, you'll get an error. Because of this, you need to wrap the alias in an Expression and use that instead.
This isn't a very clean option, so I hope someone has a better solution.

Using boolean fields with Magento ORM

I am working on a backend edit page for my custom entity. I have almost everything working, including saving a bunch of different text fields. I have a problem, though, when trying to set the value of a boolean field.
I have tried:
$landingPage->setEnabled(1);
$landingPage->setEnabled(TRUE);
$landingPage->setEnabled(0);
$landingPage->setEnabled(FALSE);
None seem to persist a change to my database.
How are you supposed to set a boolean field using magento ORM?
edit
Looking at my database, mysql is storing the field as a tinyint(1), so magento may be seeing this as an int not a bool. Still can't get it to set though.
This topic has bring curiosity to me. Although it has been answered, I'd like to share what I've found though I didn't do intense tracing.
It doesn't matter whether the cache is enabled / disabled, the table schema will be cached.
It will be cached during save process.
Mage_Core_Model_Abstract -> save()
Mage_Core_Model_Resource_Db_Abstract -> save(Mage_Core_Model_Abstract $object)
Mage_Core_Model_Resource_Db_Abstract
public function save(Mage_Core_Model_Abstract $object)
{
...
//any conditional will eventually call for:
$this->_prepareDataForSave($object);
...
}
protected function _prepareDataForSave(Mage_Core_Model_Abstract $object)
{
return $this->_prepareDataForTable($object, $this->getMainTable());
}
Mage_Core_Model_Resource_Abstract
protected function _prepareDataForTable(Varien_Object $object, $table)
{
$data = array();
$fields = $this->_getWriteAdapter()->describeTable($table);
foreach (array_keys($fields) as $field) {
if ($object->hasData($field)) {
$fieldValue = $object->getData($field);
if ($fieldValue instanceof Zend_Db_Expr) {
$data[$field] = $fieldValue;
} else {
if (null !== $fieldValue) {
$fieldValue = $this->_prepareTableValueForSave($fieldValue, $fields[$field]['DATA_TYPE']);
$data[$field] = $this->_getWriteAdapter()->prepareColumnValue($fields[$field], $fieldValue);
} else if (!empty($fields[$field]['NULLABLE'])) {
$data[$field] = null;
}
}
}
}
return $data;
}
See the line: $fields = $this->_getWriteAdapter()->describeTable($table);
Varien_Db_Adapter_Pdo_Mysql
public function describeTable($tableName, $schemaName = null)
{
$cacheKey = $this->_getTableName($tableName, $schemaName);
$ddl = $this->loadDdlCache($cacheKey, self::DDL_DESCRIBE);
if ($ddl === false) {
$ddl = parent::describeTable($tableName, $schemaName);
/**
* Remove bug in some MySQL versions, when int-column without default value is described as:
* having default empty string value
*/
$affected = array('tinyint', 'smallint', 'mediumint', 'int', 'bigint');
foreach ($ddl as $key => $columnData) {
if (($columnData['DEFAULT'] === '') && (array_search($columnData['DATA_TYPE'], $affected) !== FALSE)) {
$ddl[$key]['DEFAULT'] = null;
}
}
$this->saveDdlCache($cacheKey, self::DDL_DESCRIBE, $ddl);
}
return $ddl;
}
As we can see:
$ddl = $this->loadDdlCache($cacheKey, self::DDL_DESCRIBE);
will try to load the schema from cache.
If the value is not exists: if ($ddl === false)
it will create one: $this->saveDdlCache($cacheKey, self::DDL_DESCRIBE, $ddl);
So the problem that occurred in this question will be happened if we ever save the model that is going to be altered (add column, etc).
Because it has ever been $model->save(), the schema will be cached.
Later after he add new column and "do saving", it will load the schema from cache (which is not containing the new column) and resulting as: the data for new column is failed to be saved in database
Delete var/cache/* - your DB schema is cached by Magento even though the new column is already added to the MySQL table.

How add only not empty/null restriction to query

Do you have an idea how to write the following code nicer/shorter :)
I need to check if the field form form is not empty and then add
criterion to the query,
thanks :)
AbstractCriterion restrictions = null;
if (model.DateFrom != null)
AddRestriction(ref restrictions,
Restrictions.Ge(Projections.Property<Invoice>(x => x.DateIn), model.DateForm));
if (model.DateTo != null)
AddRestriction(ref restrictions,
Restrictions.Le(Projections.Property<Invoice>(x => x.DateIn), model.DateTo));
if (!string.IsNullOrEmpty(model.Prop1))
AddRestriction(ref restrictions,
Restrictions.Eq(Projections.Property<Invoice>(x => x.Prop1), model.Prop1));
// ... many more conditions :)
return m_session.QueryOver<Invoice>().Where(restrictions).List();
You don't need the ref keyword for starters. I think this is nicer without sacrificing readability:
var query = session.QueryOver<Invoice>();
Action<object, ICriterion> addIfNonNull = (o, c) =>
{
if (o != null)
{
query.And(c);
}
};
addIfNonNull(model.Prop1, Restrictions.Eq(Projections.Property<Invoice>(x => x.Prop1), model.Prop1));
etc.

Querying Raven with Where() only filters against the first 128 documents?

We're using Raven to validate logins so people can get into our site.
What we've found is that if you do this:
// Context is an IDocumentSession
Context.Query<UserModels>()
.SingleOrDefault(u => u.Email.ToLower() == email.ToLower());
The query only filters on the first 128 docs of the documents in
Raven. There are several thousand in our database, so unless your
email happens to be in that first 128 returned, you're out of luck.
None of the Raven samples code or any
sample code I've come across on the net performs any looping using
Skip() and Take() to iterate through the set.
Is this the desired behavior of Raven?
Is it the same behavior even if you use an advanced Lucene Query? ie; Do advanced queries behave any differently?
Is the solution below appropriate? Looks a little ugly. :P
My solution is to loop through the set of all documents until I
encounter a non null result, then I break and return .
public T SingleWithIndex(string indexName, Func<T, bool> where)
{
var pageIndex = 1;
const int pageSize = 1024;
RavenQueryStatistics stats;
var queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults == null && stats.TotalResults > pageSize)
{
for (var i = 0; i < (stats.TotalResults / (pageIndex * pageSize)); i++)
{
queryResults = Context.Query<T>(indexName)
.Statistics(out stats)
.Customize(x => x.WaitForNonStaleResults())
.Skip(pageIndex * pageSize)
.Take(pageSize)
.Where(where).SingleOrDefault();
if (queryResults != null) break;
pageIndex++;
}
}
return queryResults;
}
EDIT:
Using the fix below is not passing query params to my RavenDB instance. Not sure why yet.
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
In the end I am using Advanced Lucene Syntax instead of linq queries and things are working as expected.
RavenDB does not understand SingleOrDefault, so it performs a query without the filter. Your condition is then executed on the result set, but per default Raven only returns the first 128 documents.
Instead, you have to call
Context.Query<UserModels>()
.Where(u => u.Email == email)
.SingleOrDefault();
so the filtering is done by RavenDB/Lucene.