Using NHibernate, I have a collection of entities mapped as a dictionary.
By example, class A has a collection of B named Children, mapped as a IDictionary<int, B>. B has a property Name.
Querying class A base on some condition on B children unrelated to their dictionary indexation is quite straightforward with HQL:
from A where A.Children.Name = 'aName'
Runs flawlessly.
But for achieving the same with LINQ, this is quite less straightforward:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Fails with message could not resolve property: Values of: B
So yes, we can trick it through
IQueryable<A> query = ...;
query.Where(a => ((ICollection<B>)a.Children).Any(b => b.Name == "aName"));
That does works and yields expected results.
But this looks to me a bit ugly, I would rather not have to do that 'invalid' cast (at least 'invalid' outside of linq2NH context).
Is there any better way for querying a children collection mapped as a IDictionary with Linq and NHibernate?
As an exercise, I have decided to extend linq-to-nhibernate for supporting Values. This give a solution to the question.
There is many ways for extending linq2NH, see this list. Here, I need to add a new 'generator', as in my answer to another question.
First you need a bunch of using:
using System.Reflection;
using System.Linq.Expressions;
using System.Collections;
using System.Collections.Generic;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using NHibernate.Linq.Functions;
Then, implement HQL translation for Values.
public class DictionaryValuesGenerator : BaseHqlGeneratorForProperty
{
public override HqlTreeNode BuildHql(
MemberInfo member, Expression expression,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
// Just have to skip Values, HQL does not need it.
return visitor.Visit(expression).AsExpression();
}
}
Extend the default linq2NH registry with your generator:
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public override bool TryGetGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
if (base.TryGetGenerator(property, out generator))
return true;
return TryGetDictionaryValuesGenerator(property, out generator);
}
private DictionaryValuesGenerator _dictionaryValuesGenerator =
new DictionaryValuesGenerator();
protected bool TryGetDictionaryValuesGenerator(MemberInfo property,
out IHqlGeneratorForProperty generator)
{
generator = null;
if (property == null || property.Name != "Values")
return false;
var declaringType = property.DeclaringType;
if (declaringType.IsGenericType)
{
var genericType = declaringType.GetGenericTypeDefinition();
if (genericType != typeof(IDictionary<,>))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
if (declaringType != typeof(IDictionary))
return false;
generator = _dictionaryValuesGenerator;
return true;
}
}
I had quite a hard time figuring out how to register a generic class property generator. There is built-in support for many cases including generic dictionaries methods through derived class of GenericDictionaryRuntimeMethodHqlGeneratorBase, but apparently no support for generic dictionaries properties. So I have ended up 'hard coding' it in the TryGetGenerator method for properties.
Now configure NH to use your new registry. With hibernate.cfg.xml, add following property node under session-factory node:
<property name="linqtohql.generatorsregistry">YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName</property>
Now this does work:
IQueryable<A> query = ...;
query.Where(a => a.Children.Values.Any(b => b.Name == "aName"));
Disclaimer: done only as an exercise, I have not even committed that in my actual code. I am currently no more using any map in my mappings. I have added some just for testing, then I have undone all.
Related
Given the following:
public class Parent
{
public ChildType childType;
}
public class ChildA : Parent { ... }
public class ChildB : Parent { ... }
public enum ChildType {
childA,
childB
}
public class Content {
public long contentId;
public string? name;
public ICollection<Parent>? contentCollection; <--
...
}
I would like to use the Content class as part of an API. Is it possible to load both children into the collection just using the enum as a discriminator to determine which to cast to?
My understanding is the child objects would need to be loaded from EF as their child class first, then cast to the parent class before being added to the collection as they would be missing properties upon casting back to the child class otherwise. Is this correct? And how can the dbContext be configured to handle this when accessing through the Content class?
Apologies for all the questions, I have not done this before and cannot find an example online. I would like to know any thoughts, pointers or general info before proceeding. Please say if anything is unclear or more info is required.
Edit:
I was trying to map the child objects as their types from the DB, upcast to the parent type to be able to add multiple types to the one collection and then downcast when required for use. As far as I was aware, EF did not have the functionality to do this.
For anyone else who comes across this which needs assistance, I solved my issue by just using ADO.NET which is what entity framework is built around. I was getting stuck by trying to get this working using EF but my belief is it is not able to be done with EF.
Formatting is off a little, and I have renamed everything to suit my original question but here is the solution involved:
Writing an SP to retrieve the data similar to if the objects were stored in a TPH pattern.
Calling that SP using SQLConnection/SQLCommand. (I added this into my context class to keep DAL together but unsure if this is best practice)
public async Task<Collection<Parent>> GetModelMapCollectionAsync(long id) {
Collection<Parent> parentCollection;
using (SqlConnection connection = new SqlConnection(this.Database.GetConnectionString()))
{
using (SqlCommand sqlCommand = new SqlCommand("GetModelMapCollectionAsync", connection))
{
sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
sqlCommand.Parameters.Add(new SqlParameter("#id", id));
await connection.OpenAsync();
await sqlCommand.ExecuteNonQueryAsync();
using (SqlDataReader sqlDataReader = await sqlCommand.ExecuteReaderAsync())
{
MapCollectionResult(sqlDataReader, out parentCollection);
}
}
}
return parentCollection; }
Using a nuget package called Dapper, create row parsers for each type (easiest solution for readability/simplicity IMO)
Use the discriminator column to determine which parser to use on each row returned from the SP. This creates the child object from the row which allows it to be downcast back later.
Add that parsed object to the collection.
private void MapCollectionResult(SqlDataReader sqlDataReader, out Collection parentCollection)
{
parentCollection= new Collection();
var parentParser = sqlDataReader.GetRowParser<Parent>(typeof(Parent));
var paramClassParser = sqlDataReader.GetRowParser<ParamClass>(typeof(ParamClass));
var childAParser = sqlDataReader.GetRowParser<ChildA>(typeof(ChildA));
var childBParser = sqlDataReader.GetRowParser<ChildB>(typeof(ChildB));
ChildType type = ChildType.None;
Parent parent;
while (sqlDataReader.Read())
{
type = (ChildType)sqlDataReader["ChildTypeId"];
switch(type)
{
case ChildType.ChildA:
parent = childAParser(sqlDataReader);
break;
case ChildType.ChildB:
parent = childBParser(sqlDataReader);
break;
default:
parent = parentParser(sqlDataReader);
break;
}
parent.paramClass = paramClassParser(sqlDataReader);
parentCollection.Add(parent);
}}
How can I make LINQ query (I am using Entity Framework) that returns top n elements in a child collection?
Here are example classes
public class A {
public int ID
public ICollection<B> bData
}
public class B {
public int ID
public string Name
}
This is kind of query I was thinking;
db.A.Where(a => a.ID == query_id).Include(a => a.bData.Take(count)).ToList();
Normally this doesn't work, how could I accomplish this?
The Include method is intended for eager loading the related entity data. It's all or nothing and cannot be used for filtering, sorting, grouping etc.
So what are you asking for can be accomplished with projection (select). It would be simpler if you project to anonymous or custom type (a.k.a. DTO object), because EF does not allow projecting to entity type. But it's still doable by using anonymous type projection in LINQ to Entities query and doing second projection in LINQ to Objects to attach the filtered child collection to its parent entity like this:
var result = db.A
.Where(a => a.ID == query_id)
.Select(a => new { a, bData = a.bData.Take(count).ToList() })
.AsEnumerable() // Switch to LINQ to Object context
.Select(x =>
{
x.a.bData = x.bData;
return x.a;
})
.ToList();
Please note that in order the above to work, bData member should not be virtual or db.Configuration.LazyLoadingEnabled should be false (i.e. lazy loading should be off, otherwise the bData collections will be fully reloaded when first accessed).
To take advantage of Full text indexing on MariaDB 10, I need to use this new "MATCH AGAINST" syntax in the sql string.
http://dev.mysql.com/doc/refman/5.0/en/fulltext-search.html#function_match
I think it would be really cool if, for certain columns only, I could override linq-to-nhibernate to change the sql it generates when I use
.Where(x => FullTextIndexedStringProperty.Contains("Some word")).ToList().
Who can give me some general directions on how to get started?
This will get you a very simple MATCH ... AGAINST clause. If you want to get more complex (more arguments, specifying the search modifier), you'll have to make some bigger changes. Hopefully this will get you started though:
Create a new dialect and register a simple MATCH (...) AGAINST (...) function:
public class CustomMySQLDialect : MySQLDialect
{
public CustomMySQLDialect()
{
this.RegisterFunction(
"matchagainst",
new SQLFunctionTemplate(
NHibernateUtil.Boolean,
"match (?1) against (?2)"));
}
}
Create a static extension method on string that you'll use in LINQ statements:
public static class LinqExtensions
{
public static bool MatchAgainst(this string source, string against)
{
throw new NotImplementedException();
}
}
Create a new LINQ to HQL generator class that associates the method with the SQL function we registered in the custom dialect:
public class MatchAgainstGenerator : BaseHqlGeneratorForMethod
{
public MatchAgainstGenerator()
{
this.SupportedMethods = new[]
{
ReflectionHelper.GetMethod(() => LinqExtensions.MatchAgainst(null, null))
};
}
public override HqlTreeNode BuildHql(
MethodInfo method,
System.Linq.Expressions.Expression targetObject,
ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
return treeBuilder.BooleanMethodCall(
"matchagainst",
arguments.Select(visitor.Visit).Cast<HqlExpression>());
}
}
Create a custom LinqToHqlGeneratorsRegistry:
public class MyLinqToHqlRegistry : DefaultLinqToHqlGeneratorsRegistry
{
public MyLinqToHqlRegistry()
{
var generator = new MatchAgainstGenerator();
RegisterGenerator(typeof(LinqExtensions).GetMethod("MatchAgainst"), generator);
}
}
Use your custom dialect, and Linq to HQL registry either in your cfg.xml file or in code:
var cfg = new Configuration()
.DataBaseIntegration(db =>
{
db.Dialect<CustomMySQLDialect>();
})
.LinqToHqlGeneratorsRegistry<MyLinqToHqlRegistry>();
Finally, use your extension method in a LINQ-to-NHibernate query:
session.Query<Article>()
.Where(a => a.Body.MatchAgainst("configured"))
.ToList()
.Dump();
This will generate SQL that looks like this:
select
userquery_0_.Id as Id51_,
userquery_0_.Title as Title51_,
userquery_0_.Body as Body51_
from
articles userquery_0_
where
match (userquery_0_.Body) against ('configured');
Again, this won't help if you have more complicated requirements. But hopefully this is at least a good starting point.
In case anyone is curious about how to make this support more complex scenarios, here are the problems I think you'd run into:
Separating the arguments to MATCH from those to AGAINST.
Registering a custom SQL function with NHibernate that can take an arbitrary number of arguments in different places
Creating the correct HQL even after solving the two issues above.
I have this piece of code in a TaskRepository :
public List<Task> GetActiveTasks()
{
return SessionContainer.Session
.Query<Task>()
.Where(t => t.IsActive())
.ToList();
}
And this is the IsActive() method in the Task Class
public virtual bool IsActive()
{
return States.ToList().Max().Name == "Active";
}
My problem is that GetActiveTasks() returns NotSupportedException.
Does anyone knows whats the problem ? I think it is not possible to call instance methods in the Where predicate. If that is not possible, is there any workaround for this? I need to get all the active tasks but I dont know how to do it if it is not like this.
The lambdas given in the Query have to be translated to sql. How should NHibernate interprete a method implemented by you? it can't. However you could map IsActive as a Formula property and query that. Something like:
Map(x => x.IsActive).Formula("(SELECT ... FROM (Select s.Name FROM States s WHERE s.task_id = Id ORDER BY Id desc LIMIT 1) maxState WHERE maxState.Name == Active)")
One option is to refactor the Task class like this:
public static Expression<Func<Task, bool>> IsActiveExpr =
(task) => task.States.Max().Name == "Active";
private static Func<Task, bool> _IsActiveCompiled = IsActiveExpr.Compile();
public virtual bool IsActive()
{
return IsActiveCompiled(this);
}
Now you can use IsActiveExpr in LINQ queries.
Another options is to extend the NHibernate query provider so that it recognizes the IsActive() method directly. Do a search, or see for instance my reply to this question: Lambda string as VARCHAR. But care might need to be taken to avoid duplicating logic.
I'm using mapping by code in NHibernate.
I got a class with several properties. One of them is not related to any columns in DB but still has getter and setter.
I use ConventionModelMapper not ModelMapper. The first one assumes that all properties are mapped.
How i can tell to NHibernate to ignore it?
I find it easier to just create an attribute, attach that attribute to the property, and check for it in the mapper.IsPersistentProperty method. Something like this:
class IngnoreAttribute : Attribute
{
}
class Foo
{
[Ignore]
public virtual string Bar { get; set; }
}
mapper.IsPersistentProperty((mi, declared) => mi.GetCustomAttribute<IgnoreAttribute>() == null);
This way, I don't have to keep a list of properties to be ignored at the mapping codes.
Why not map the properties you want and leave the ones not needed to be mapped
check this
You can manage the persistence of ConventionModelMapper as following:
mapper.BeforeMapProperty += (mi, propertyPath, map) =>
{
// Your code here using mi, propertyPath, and map to decide if you want to skip the property .. can check for property name and entity name if you want to ignore it
};
A better answer would be:
mapper.IsPersistentProperty((mi, declared) =>
{
if (mi.DeclaringType == typeof (YourType) && mi.Name == "PropertyNameToIgnore")
return false;
return true;
});
If you do not mention the property that should be ignored in your NHibernate mapping, NHibernate will ignore it.