NHibernate LinqToHqlGenerator for SQL Server 2008 full text index 'Containing' keyword - sql-server-2008

I think I'm missing something fundamental when implementing a LinqToHql generator class.
I've successfully registered the SQL Server 2008 contains query using a custom dialect with this registration:
RegisterFunction("contains", new StandardSQLFunction("contains", null));
I have only one class with a full text index to be queried:
public class SearchName
{
public virtual Guid Id {get; set;}
public virtual string Name {get; set;} // this is the search field
}
The contains function works properly in HQL:
var names = Session.CreateQuery("from SearchName where contains(Name,:keywords)")
.SetString("keywords", "john")
.List();
and the generated SQL is perfect:
select searchname0_.Id as Id4_,
searchname0_.Name as Name4_
from Search_Name searchname0_
where contains(searchname0_.Name, 'john' /* #p0 */)
The next challenge was to implement the Linq to HQL generator:
public class MyLinqtoHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqtoHqlGeneratorsRegistry()
{
this.Merge(new ContainsGenerator());
}
}
public class ContainsGenerator : BaseHqlGeneratorForMethod
{
public ContainsGenerator()
{
SupportedMethods = new[] {
ReflectionHelper.GetMethodDefinition<SearchName>(d => d.Name.Contains(String.Empty))
};
}
public override HqlTreeNode BuildHql(MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
return treeBuilder.MethodCall("contains",
visitor.Visit(targetObject).AsExpression(),
visitor.Visit(arguments[0]).AsExpression()
);
}
}
}
Calling the method like this:
var namesLinq = Session.Query<SearchName>().Where(x=> x.Name.Contains("john")).ToList();
Unfortunately, this doesn't seem to override the built-in Contains method, and the generated SQL is wrong:
select searchname0_.Id as Id4_,
searchname0_.Name as Name4_
from Search_Name searchname0_
where searchname0_.Name like ('%' + 'john' /* #p0 */ + '%')
Is it not possible to override the default Contains method, or have I just made a silly mistake?
PS - I'm using NHibernate 3.3.1.4000

OK, I've finally figured it out!
First, I managed to delete the registration code from my configuration:
...
.ExposeConfiguration(cfg =>
{
cfg.LinqToHqlGeneratorsRegistry<MyLinqtoHqlGeneratorsRegistry>();
...
}
Second, don't try to override the existing Linq behaviors. I moved my Contains extension method to the full-text class.
Third, build the Hql tree correctly.
For others trying to implement a SQL 2008 Free-text contains search, here's the complete implementation:
public static class DialectExtensions
{
public static bool Contains(this SearchName sn, string searchString)
{
// this is just a placeholder for the method info.
// It does not otherwise matter.
return false;
}
}
public class MyLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqtoHqlGeneratorsRegistry()
: base()
{
RegisterGenerator(ReflectionHelper.GetMethod(() =>
DialectExtensions.Contains(null, null)),
new ContainsGenerator());
}
}
public class ContainsGenerator : BaseHqlGeneratorForMethod
{
string fullTextFieldName = "Name";
public ContainsGenerator()
: base()
{
SupportedMethods = new[] {
ReflectionHelper.GetMethodDefinition(() =>
DialectExtensions.Contains(null, null))
};
}
public override HqlTreeNode BuildHql(MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// cannot figure out how to interrogate the model class to get an
// arbitrary field name...
// perhaps the RegisterGenerator() call above could be used to pass a
// property name to the ContainsGenerator constructor?
// in our case, we only have one full text searchable class, and its
// full-text searchable field is "Name"
HqlExpression[] args = new HqlExpression[2] {
treeBuilder.Ident(fullTextFieldName).AsExpression(),
visitor.Visit(arguments[1]).AsExpression()
};
return treeBuilder.BooleanMethodCall("contains", args);
}
}
For the above to work, you must have declared and used your custom dialect:
public class CustomMsSql2008Dialect : NHibernate.Dialect.MsSql2008Dialect
{
public CustomMsSql2008Dialect()
{
RegisterFunction(
"contains",
new StandardSQLFunction("contains", null)
);
}
}
Then you can use your new contains search this way:
var namesLinq = Session.Query<SearchName>().Where(x => x.Contains("john")).ToList();
... and the resulting SQL is perfect! (at least if you only have one table you're performing full-text searches on)
EDIT: UPDATED IMPLEMENTATION TO SUPPORT MORE THAN ONE FULLTEXT 'Contains' SEARCH PER QUERY.
Here's the revised version:
public static class DialectExtensions
{
public static bool FullTextContains(this string source, string pattern)
{
return false;
}
}
public class MyLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqtoHqlGeneratorsRegistry()
: base()
{
RegisterGenerator(ReflectionHelper.GetMethod(() => DialectExtensions.FullTextContains(null, null)),
new FullTextContainsGenerator());
}
}
public class FullTextContainsGenerator : BaseHqlGeneratorForMethod
{
public FullTextContainsGenerator()
{
SupportedMethods = new[] { ReflectionHelper.GetMethod(() => DialectExtensions.FullTextContains(null, null)) };
}
public override HqlTreeNode BuildHql(MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression[] args = new HqlExpression[2] {
visitor.Visit(arguments[0]).AsExpression(),
visitor.Visit(arguments[1]).AsExpression()
};
return treeBuilder.BooleanMethodCall("contains", args);
}
}
To use the revised version, the syntax is slightly different:
var namesLinq = Session.Query<SearchName>().Where(x => x.Name.FullTextContains("john")).ToList();

Related

ModelBindingContext.ValueProvider.GetValue(key) always returns null

I'm using AngularJS to manipulate a fairly complex parent object with children that need to behave quite differently server-side. Based on this answer, which appears pretty solid, I've created the test case below. The issue I'm running into is that whenever I enter the CreateModel function, any call to bindingContext.ValueProvider.GetValue(key) returns null. I've checked all values in the debugger. The object type appears to be loaded, but no values have yet been bound.
My Models:
public class Menagerie
{
public Menagerie()
{
Critters = new List<Creature>();
}
public string MakeNoise()
{
return String.Join(" ", Critters.Select(c => c.MakeNoise()));
}
public List<Creature> Critters { get; set; }
}
public class Tiger : Creature
{
public Tiger() { }
public override CreatureType Type => CreatureType.Tiger;
public override string Sound => "ROAR";
}
public class Kitty : Creature
{
public Kitty() { }
public override CreatureType Type => CreatureType.Kitty;
public override string Sound => "meow";
}
public class Creature
{
public Creature() { }
public string Name { get; set; }
public virtual CreatureType Type { get; set; }
public virtual string Sound { get; }
public string MakeNoise()
{
return $"{Name} says {Sound ?? "nothing"}.";
}
public static Type SelectFor(CreatureType type)
{
switch (type)
{
case CreatureType.Tiger:
return typeof(Tiger);
case CreatureType.Kitty:
return typeof(Kitty);
default:
throw new Exception();
}
}
}
public enum CreatureType
{
Tiger,
Kitty,
}
public class CreatureModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
CreatureType creatureType = GetValue<CreatureType>(bindingContext, "Type");
Type model = Creature.SelectFor(creatureType);
Creature instance = (Creature)base.CreateModel(controllerContext, bindingContext, model);
bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, model);
return instance;
}
private T GetValue<T>(ModelBindingContext bindingContext, string key)
{
ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(key); // valueResult is null
bindingContext.ModelState.SetModelValue(key, valueResult);
return (T)valueResult.ConvertTo(typeof(T)); // NullReferenceException
}
}
My script:
(function () {
'use strict';
angular.module('app', []).controller('CritterController', ['$scope', '$http', function ($scope, $http) {
$http.post('/a/admin/get-menagerie', { }).success(function (data) {
$scope.menagerie = data.menagerie;
});
$scope.makeNoise = function () {
$http.post('/a/admin/make-noise', { menagerie: $scope.menagerie }).success(function (data) {
$scope.message = data.message;
});
}
}]);
})();
Things I've tried
I've tried just using a string to indicate the class name, as in this answer and this one. However, the call to bindingContext.ValueProvider.GetValue(key) still returns null, resulting in a NullReferenceException.
I also checked to ensure the model is binding properly. When I change my CreatureModelBinder to the following, everything maps fine. But each creature loses its inherited type and becomes a Creature.
public class CreatureModelBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
return base.CreateModel(controllerContext, bindingContext, modelType);
}
} // MakeNoise returns: "Shere Khan says nothing. Mr. Boots says nothing. Dr. Evil says nothing."
The solution I was hoping existed:
When posting JSON data, it appears that all keys have the model name prepended. This can be solved simply by changing the first line of CreateModel to:
CreatureType creatureType = GetValue<CreatureType>(bindingContext, bindingContext.ModelName + ".Type");
Previous Answer:
The data my AngularJS function posts is labeled by Chrome as "Request Payload", which cannot (as far as I can tell) be accessed in CreateModel. When I implemented #DarinDimitrov's solutions line-for-line, the data was posted as "Form Data", which was available in CreateModel. I found a little more info about the difference between data types here, though AngularJS doesn't appear to be able to send data with a content-type other than application/json without some fancy acrobatics.
I did, however, find that I can access an instance of my object (and create a new instance with a different type) in the BindModel function as follows:
public class CreatureModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
Creature instance = (Creature)base.BindModel(controllerContext, bindingContext);
Creature newInstance = (Creature)Activator.CreateInstance(Creature.SelectFor((instance).Type));
newInstance.Name = instance.Name;
return newInstance;
}
} // MakeNoise() returns: "Shere Khan says ROAR. Mr. Boots says meow. Dr. Evil says meow."
The greatest downside I can see is that every property in the object must be manually mapped to the new instance, which can become cumbersome and produce hard-to-track bugs.
This is what I'm using for now, though I'm open to suggestions if anyone can provide a more elegant solution.
EDIT
I found that when posting as application/json, the model data can be accessed using this:

DTO to specific object conversion pattern

The architecture: Win8 app + local Web API Self-Host share a common "Contracts" project.
The Web API returns very general contract types (IEnumerable etc.).
Within the Win8 app I want to convert these contracts to concrete MVVM compatible model objects which use ObservableCollection for example instead of IEnumerables.
I would have loved to use AutoMapper for this task but it is not compatible with the WinRT.
I used AutoMapper some time ago, but now I generally use a specific class to do this work so I can test it and implement "strange" logic. This class is responsible for the mapping in the 2 direction (if both are needed).
Sometimes, because I'm lazy ;-), I have used an implicit conversion operator to simplify the conversion, but I think that conceptually a constructor for the dto could be better:
public class ItemDto
{
public Int32 Id { get; set; }
public String Description { get; set; }
public static implicit operator ItemDto (Item item)
{
var dto = new ItemDto()
{
Id = item.Id,
Description = item.LongDescription
};
return dto;
}
In all these cases, I think that the possibility to test your mapping has a great value.
You can to use reflection ( System.Reflection) for mapper yours DTOs by yourself, in a loop by the properties and mapping using the portable CLR types.
Thank you for your suggestions.
I solved it in a non-generic fashion, for every model I do have a specific converter that does the job. What do you think?
using Project.Contracts;
using Project.Models;
namespace Project.Converters.Contracts
{
public static class ProductConverter
{
public static ProductContract ToContract(this Product model)
{
if (model == null)
{
return new ProductContract();
}
return new ProductContract
{
Id = model.Id,
Name = mode.Name,
Tags = model.Tags.ToContracts()
};
}
public static ICollection<ProductContract> ToContracts(this IEnumerable<Product> models)
{
if (models == null)
{
return new Collection<ProductContract>();
}
return models.Select(m => m.ToContract()).ToList();
}
public static Product ToModel(this ProductContract contract)
{
if (contract == null)
{
return new Product();
}
return new Product
{
Id = contract.Id,
Name = contract.Name,
Tags = contract.Tags.ToModels()
};
}
public static ObservableCollection<Product> ToModels(this IEnumerable<ProductContract> contracts)
{
if (contracts == null)
{
return new ObservableCollection<Product>();
}
return new ObservableCollection<Product>(contracts.Select(c => c.ToModel()));
}
}
}

Trouble Passing Parameter to LinqToSql Stored Procedure

public IEnumerable<T> ExecuteStoredProcedure<T>(params object[] parameters)
{
Type genericType = typeof(T);
string commandthing = genericType.Name.Replace("Result", "");
//_db is my Linq To Sql database
return _db.ExecuteQuery<T>(commandthing, parameters).AsEnumerable();
}
The stored procedure is named GetOrder and has a single int parameter of orderid. I'm calling the above like so:
SqlParameter parm1 = new SqlParameter("#orderid", SqlDbType.Int);
parm1.Value = 123;
var results =
_session.ExecuteStoredProcedure<GetOrderResult>(parm1).Single();
I'm receiving the following error: A query parameter cannot be of type 'System.Data.SqlClient.SqlParameter'
Thoughts? Or am I just missing something obvious?
Update: I'm trying to make this as generic as possible...my current thinking is that I'm going to have to do some string trickery to create the ExecuteQuery text and parameters.
Update: Posting below my Session Interface and my Linq to Sql Implementation of the interface...hopefully that will clarify what I'm attempting to do
public interface ISession : IDisposable
{
void CommitChanges();
void Delete<T>(Expression<Func<T, bool>> expression) where T : class;
void Delete<T>(T item) where T : class;
void DeleteAll<T>() where T : class;
T Single<T>(Expression<Func<T, bool>> expression) where T : class;
IQueryable<T> All<T>() where T : class;
void Add<T>(T item) where T : class;
void Add<T>(IEnumerable<T> items) where T : class;
void Update<T>(T item) where T : class;
IEnumerable<T> ExecuteStoredProcedure<T>(params object[] parameters);
}
public class LinqToSqlSession : ISession
{
public readonly Db _db;
public LinqToSqlSession()
{
_db = new Db(ConfigurationManager.ConnectionStrings[Environment.MachineName].ConnectionString);
}
public void CommitChanges()
{
_db.SubmitChanges();
}
/// <summary>
/// Gets the table provided by the type T and returns for querying
/// </summary>
private Table<T> GetTable<T>() where T : class
{
return _db.GetTable<T>();
}
public void Delete<T>(Expression<Func<T, bool>> expression) where T : class
{
var query = All<T>().Where(expression);
GetTable<T>().DeleteAllOnSubmit(query);
}
public void Delete<T>(T item) where T : class
{
GetTable<T>().DeleteOnSubmit(item);
}
public void DeleteAll<T>() where T : class
{
var query = All<T>();
GetTable<T>().DeleteAllOnSubmit(query);
}
public void Dispose()
{
_db.Dispose();
}
public T Single<T>(Expression<Func<T, bool>> expression) where T : class
{
return GetTable<T>().SingleOrDefault(expression);
}
public IEnumerable<T> ExecuteStoredProcedure<T>(params object[] parameters)
{
Type genericType = typeof(T);
string commandstring = genericType.Name.Replace("Result", "");
//_db is my Linq To Sql database
return _db.ExecuteQuery<T>(commandstring, parameters).AsEnumerable();
}
public IQueryable<T> All<T>() where T : class
{
return GetTable<T>().AsQueryable();
}
public void Add<T>(T item) where T : class
{
GetTable<T>().InsertOnSubmit(item);
}
public void Add<T>(IEnumerable<T> items) where T : class
{
GetTable<T>().InsertAllOnSubmit(items);
}
public void Update<T>(T item) where T : class
{
//nothing needed here
}
}
That isn't how you're supposed to wire up Stored Procedures with Linq-to-SQL. You should extend the DataContext and use ExecuteMethodCall instead:
Taken from MSDN:
public partial class MyDataContext
{
[Function()]
public IEnumerable<Customer> CustomerById(
[Parameter(Name = "CustomerID", DbType = "NChar(5)")]
string customerID)
{
IExecuteResult result = this.ExecuteMethodCall(this,
((MethodInfo)(MethodInfo.GetCurrentMethod())),
customerID);
return (IEnumerable<Customer>)(result.ReturnValue);
}
}
If you really must execute a sproc as a query (highly not recommended), then you have to preface the command with EXEC, and don't use SqlParameter either, the call would look like:
var results = context.ExecuteQuery<MyResult>("EXEC usp_MyProc {0}, {1}",
custID, custName);
(And I'll note, pre-emptively, that this is not a SQL injection vector because Linq to SQL turns the curly braces into a parameterized query.)
Read about how to call sprocs in linq to sql
http://weblogs.asp.net/scottgu/archive/2007/08/16/linq-to-sql-part-6-retrieving-data-using-stored-procedures.aspx
Had the same Problem. The following approach worked 4 me.
public interface IBusinessEntityRepository
{
.......
object CallStoredProcedure(string storedProcedureName, object[] parameters);
}
implementation in my linqtosql GenericLinqRepository
public object CallStoredProcedure(string storedProcedureName, object[] parameters)
{
DataContext dataContext = GetCurrentDataContext();
MethodInfo method = dataContext.GetType().GetMethod(storedProcedureName);
return method.Invoke(dataContext, parameters);
}
I'm sure there is a better way to do this...but this is presently working:
public IEnumerable<T> ExecuteStoredProcedure<T>(params object[] parameters)
{
Type genericType = typeof(T);
StringBuilder sb=new StringBuilder();
sb.Append("EXEC ");
sb.Append(genericType.Name.Replace("Result", " " ));
for (int i = 0; i < parameters.Count(); i++)
{
sb.Append("{" + i.ToString() + "} ");
}
string commandstring = sb.ToString();
return _db.ExecuteQuery<T>(commandstring, parameters);
}
It's a little bit brittle in that your parameters must be set up in the proper order, and it's probably offensive to some...but it does accomplish the goal.
You can use this instead:
new SqlParameter { ParameterName = "UserID", Value =txtuserid.Text }
This equivalent in System.Data.SqlClient to :
SqlParameter[] param=new SqlParameter[2];
param[0]=new SqlParameter("#UserID",txtuserid)

AutoMapper : Site wide usage of IValueFormatter for given types

It is my understanding I can configure AutoMapper in the following way and during mapping it should format all source model dates to the rules defined in the IValueFormatter and set the result to the mapped model.
ForSourceType<DateTime>().AddFormatter<StandardDateFormatter>();
ForSourceType<DateTime?>().AddFormatter<StandardDateFormatter>();
I get no effect for my mapped class with this. It only works when I do the following:
Mapper.CreateMap<Member, MemberForm>().ForMember(x => x.DateOfBirth, y => y.AddFormatter<StandardDateFormatter>());
I am mapping DateTime? Member.DateOfBirth to string MemberForm.DateOfBirth. The formatter basically creates a short date string from the date.
Is there something I am missing when setting the default formatter for a given type?
Thanks
public class StandardDateFormatter : IValueFormatter
{
public string FormatValue(ResolutionContext context)
{
if (context.SourceValue == null)
return null;
if (!(context.SourceValue is DateTime))
return context.SourceValue.ToNullSafeString();
return ((DateTime)context.SourceValue).ToShortDateString();
}
}
I had the same problem and found a fix. Try changing:
ForSourceType<DateTime>().AddFormatter<StandardDateFormatter>();
To
Mapper.ForSourceType<DateTime>().AddFormatter<StandardDateFormatter>();
FYI - AddFormatter method is obsolete in 3.0 version. You can use ConvertUsing instead:
Mapper.CreateMap<DateTime, string>()
.ConvertUsing<DateTimeCustomConverter>();
public class DateTimeCustomConverter : ITypeConverter<DateTime, string>
{
public string Convert(ResolutionContext context)
{
if (context.SourceValue == null)
return null;
if (!(context.SourceValue is DateTime))
return context.SourceValue.ToNullSafeString();
return ((DateTime)context.SourceValue).ToShortDateString();
}
}
I am using AutoMapper v1.
There is an abstract class there that does most of the grunt work called ValueFormatter.
My Code:
public class DateStringFormatter : ValueFormatter<DateTime>
{
protected override string FormatValueCore(DateTime value)
{
return value.ToString("dd MMM yyyy");
}
}
Then in my Profile class:
public sealed class ViewModelMapperProfile : Profile
{
...
protected override void Configure()
{
ForSourceType<DateTime>().AddFormatter<DateStringFormatter>();
CreateMap<dto, viewModel>()
.ForMember(dto => dto.DateSomething, opt => opt.MapFrom(src => src.DateFormatted));
}

linq2sql missing event model?

How come the "Table" classes Generated in the Dbml do not contain useful events like
OnBeforeInsert
OnBeforeUpdate
OnAfterInsert
etc.
Am I missing something?
This question is related to frustration trying to set timestamp columns.
UPDATE
I created the following method of doing this neatly what does everyone think?
public class Model
{
internal virtual void OnBeforeInsert()
{
}
internal virtual void OnBeforeUpdate()
{
}
}
public partial class DbDataContext
{
public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
{
foreach (var insert in this.GetChangeSet().Inserts)
{
if (insert is Model)
{
((Model)insert).OnBeforeInsert();
}
}
foreach (var update in this.GetChangeSet().Updates)
{
if (update is Model)
{
((Model)update).OnBeforeUpdate();
}
}
base.SubmitChanges(failureMode);
}
}
public partial class Address : Model
{
internal override void OnBeforeInsert()
{
var created = DateTime.Now;
this._Modified = created;
this._Created = created;
}
}
I had a similar issue like this recently.
There is a partial method in the generated class for "OnValidate". Simply declaring the method in your partial will force it to be called (vb.net does not support partial methods like c#) or in c# simply declare a partial method.
The method is passed a System.Data.Linq.ChangeAction enum that is either: Delete, Insert, Update, or None.
Below is a sample of what you did using the built in partial method.
public partial class Address
{
private partial void OnValidate(System.Data.Linq.ChangeAction action)
{
if (action == System.Data.Linq.ChangeAction.Insert)
{
var created = DateTime.Now;
this._Modified = created;
this._Created = created;
} else if (action == System.Data.Linq.ChangeAction.Update) {
this._Modified = DateTime.Now;
}
}
}