diff --git a/doc/reference/master.xml b/doc/reference/master.xml index 335216513d4..b28a8eb5148 100644 --- a/doc/reference/master.xml +++ b/doc/reference/master.xml @@ -17,6 +17,7 @@ + @@ -67,6 +68,7 @@ &query-hql; &query-criteria; &query-queryover; + &query-linq; &query-sql; &filters; diff --git a/doc/reference/modules/query_linq.xml b/doc/reference/modules/query_linq.xml new file mode 100644 index 00000000000..32e0e041aa1 --- /dev/null +++ b/doc/reference/modules/query_linq.xml @@ -0,0 +1,660 @@ + + Linq Queries + + + NHibernate 3.0 introduces the Linq to NHibernate provider, which allows the use of the Linq API + for querying with NHibernate. + + + The Linq provider works as an extension of the ISession. It is defined in the + NHibernate.Linq namespace, so this namespace has to be imported for using the + Linq provider. Of course, the LINQ namespace is still needed too. + + + + Note: NHibernate has another querying API which uses lambda, QueryOver. + It should not be confused with a LINQ provider. + + + + Structure of a Query + + + Queries are created from an ISession using the syntax: + + cats = + session.Query() + .Where(c => c.Color == "white") + .ToList();]]> + + The Query<TEntity> function yields an IQueryable<TEntity>, + with which Linq extension methods or Linq syntax can be used. When executed, the IQueryable<TEntity> + will be translated to a SQL query on the database. + +   + + + It is possible to query a specific sub-class while still using a queryable of the base class. + + cats = + session.Query("Eg.DomesticCat, Eg") + .Where(c => c.Name == "Max") + .ToList();]]> + + + A client timeout for the query can be defined. + + cats = + session.Query() + .Where(c => c.Color == "black") + // Allows 10 seconds only. + .TimeOut(10) + .ToList();]]> + + + + Parameter types + + + Query parameters get extracted from the Linq expression. Their types are selected according to + NHibernate types default for .Net types. + + + The MappedAs extension method allows to override the default type. + + cats = + session.Query() + .Where(c => c.BirthDate == DateTime.Today.MappedAs(NHibernateUtil.Date)) + .ToList();]]> + + + + Supported methods and members + + + Many methods and members of common .Net types are supported by the Linq to NHibernate provider. + They will be translated to the appropriate SQL, provided they are called on an entity property + (or expression deriving from) or at least one of their arguments references an entity property. + (Otherwise, their return values will be evaluated with .Net runtime before query execution.) + + + + Common methods + + + The .Net 4 CompareTo method of strings and numerical types is translated to + a case statement yielding -1|0|1 according to the result + of the comparison. + +   + + + Many type conversions are available. For all of them, .Net overloads with more than one argument + are not supported. + + + Numerical types can be converted to other numerical types or parsed from strings, using + following methods: + + + + + Convert.ToDecimal + + + + + Convert.ToDouble + + + + + Convert.ToInt32 + + + + + Decimal.Parse + + + + + Double.Parse + + + + + Int32.Parse + + + + + Strings can be converted to Boolean and DateTime with + Convert.ToBoolean or Boolean.Parse and + Convert.ToDateTime or DateTime.Parse respectively. + + + On all types supporting string conversion, ToString method can be called. + + catBirthDates = + session.Query() + .Select(c => c.BirthDate.ToString()) + .ToList();]]> +   + + + Equals methods taking a single argument with the same type can be used. Of + course, == is supported too. + + + + + <literal>DateTime</literal> and <literal>DateTimeOffset</literal> + + + Date and time parts properties can be called on DateTime and DateTimeOffset. + Those properties are: + + + + + Date + + + + + Day + + + + + Hour + + + + + Minute + + + + + Month + + + + + Second + + + + + Year + + + + + + + <literal>ICollection</literal>, non generic and generic + + + Collections Contains methods are supported. + + catsWithWrongKitten = + session.Query() + .Where(c => c.Kittens.Contains(c)) + .ToList();]]> + + + + <literal>IDictionary</literal>, non generic and generic + + + Dictionaries Item getter are supported. This enables referencing a dictionary + item value in a where condition, as it can be done with + HQL expressions. + + + Non generic dictionary method Contains and generic dictionary method + ContainsKey are translated to corresponding indices + HQL expressions. Supposing Acts + in following HQL example is generic, + + + + it could be written with Linq: + + shows = + session.Query() + .Where(s => s.Acts.ContainsKey("fizard")) + .ToList();]]> + + + + Mathematical functions + + + The following list of mathematical functions from System.Math is handled: + + + + + + Trigonometric functions: Acos, Asin, Atan, + Atan2, Cos, Cosh, Sin, + Sinh, Tan, Tanh + + + + + Abs (all overloads) + + + + + Ceiling (both overloads) + + + + + Floor (both overloads) + + + + + Pow + + + + + Round (only overloads without a mode argument) + + + + + Sign (all overloads) + + + + + Sqrt + + + + + Truncate (both overloads) + + + + + + + Nullables + + + On Nullable<> types, GetValueOrDefault methods, with or + without a provided default value, are supported. + + + + + Strings + + Following properties and methods are supported on strings: + + + + + Contains + + + + + EndsWith (without additional parameters) + + + + + IndexOf (only overloads taking a character or a string, and optionally a start index) + + + + + Length + + + + + Replace (both overloads) + + + + + StartsWith (without additional parameters) + + + + + Substring (both overloads) + + + + + ToLower (without additional parameters) and ToLowerInvariant, + both translated to the same database lower function. + + + + + ToUpper (without additional parameters) and ToUpperInvariant, + both translated to the same database upper function. + + + + + Trim (both overloads) + + + + + TrimEnd + + + + + TrimStart + + + + +   + + + Furthermore, a string Like extension methods allows expressing SQL + like conditions. + + cats = + session.Query() + .Where(c => c.Name.Like("L%l%l")) + .ToList();]]> + + This Like extension method is a Linq to NHibernate method only. Trying to call it + in another context is not supported. + + + If you want to avoid depending on the NHibernate.Linq namespace, + you can define your own replica of the Like methods. Any 2 or 3 arguments method + named Like in a class named SqlMethods will be translated. + + + + + + Future results + + + Future results are supported by the Linq provider. They are not evaluated till one gets executed. + At that point, all defined future results are evaluated in one single round-trip to database. + + cats = + session.Query() + .Where(c => c.Color == "black") + .ToFuture(); +IFutureValue catCount = + session.Query() + .ToFutureValue(q => q.Count()); +// Execute them +foreach(Cat cat in cats) +{ + // Do something +} +if (catCount.Value > 10) +{ + // Do something +} +]]> + + In above example, accessing catCount.Value does not trigger a round-trip to database: + it has been evaluated with cats enumeration. + + + + + Fetching associations + + + A Linq query may load associated entities or collection of entities. Once the query is defined, using + Fetch allows fetching a related entity, and FetchMany allows + fetching a collection. + + oldCats = + session.Query() + .Where(c => c.BirthDate.Year < 2010) + .Fetch(c => c.Mate) + .FetchMany(c => c.Kittens) + .ToList();]]> + + Issuing many FetchMany on the same query may cause a cartesian product over + the fetched collections. This can be avoided by splitting the fetches among + future queries. + + oldCatsQuery = + session.Query() + .Where(c => c.BirthDate.Year < 2010); +oldCatsQuery + .Fetch(c => c.Mate) + .FetchMany(c => c.Kittens) + .ToFuture(); +IList oldCats = + oldCatsQuery + .FetchMany(c => c.AnotherCollection) + .ToFuture() + .ToList();]]> +   + + + Use ThenFetch and ThenFetchMany for fetching associations + of the previously fetched association. + + oldCats = + session.Query() + .Where(c => c.BirthDate.Year < 2010) + .Fetch(c => c.Mate) + .FetchMany(c => c.Kittens) + .ThenFetch(k => k.Mate) + .ToList();]]> + + + + Query cache + + + The Linq provider can use the query cache if it is setup. Refer to + for more details on how to set it up. + +   + + + Cacheable extension method enables the cache for the query. + + oldCats = + session.Query() + .Where(c => c.BirthDate.Year < 2010) + .Cacheable() + .ToList();]]> +   + + + CacheMode and CacheRegion extension methods set + the cache mode and the cache region respectively. + + cats = + session.Query() + .Where(c => c.Name == "Max") + .Cacheable() + .CacheRegion("catNames") + .ToList();]]> + + + + Extending the Linq to NHibernate provider + + + The Linq to NHibernate provider can be extended for supporting additional SQL functions or + translating additional methods or properties to a SQL query. + + + + Adding SQL functions + + NHibernate Linq provider feature a LinqExtensionMethod attribute. It allows using an + arbitrary, built-in or user defined, SQL function. It should be applied on a method having the same + arguments than the SQL function. + + + + Then it can be used in a Linq to NHibernate query. + + cats = + session.Query() + // Pseudo random order + .OrderBy(c => (c.Id * rnd).Checksum()) + .ToList();]]> + + The function name is inferred from the method name. If needed, another name can be provided. + + + + It is required that at least one of the parameters of the method call has its value originating + from an entity. Otherwise, the Linq provider will try to evaluate the method call with .Net + runtime. + + + + + Adding a custom generator + + Generators are responsible for translating .Net method calls found in lambdas to the proper HQL + constructs. Adding support for a new method call can be achieved by registering an additional + generator in the Linq to NHibernate provider. + + + If the purpose of the added method is to simply call some SQL function, using + will be easier. + +   + + As an example, here is how to add support for an AsNullable method which + would allow to call aggregates which lay yield null without to explicitly + cast to the nullable type of the aggregate. + + (this T value) where T : struct + { + // Allow runtime use. + // Not useful for linq-to-nhibernate, could be: + // throw NotSupportedException(); + return value; + } +}]]> + + Adding support in Linq to NHibernate for this custom method requires a generator. For this + AsNullable method, we need a method generator, declaring statically its + supported method. + + NullableExtensions.AsNullable(0)) + }; + } + + public override HqlTreeNode BuildHql(MethodInfo method, + Expression targetObject, + ReadOnlyCollection arguments, + HqlTreeBuilder treeBuilder, + IHqlExpressionVisitor visitor) + { + // This has just to transmit the argument "as is", HQL does not need a specific call for + // null conversion. + return visitor.Visit(arguments[0]).AsExpression(); + } +}]]> + + There are property generators too, and the supported methods or properties can be + dynamically declared. Check NHibernate NHibernate.Linq.Functions + namespace classes's sources for more examples. CompareGenerator + and DateTimePropertiesHqlGenerator are examples of those other cases. + + + For adding AsNullableGenerator in Linq to NHibernate provider, a new + generators registry should be used. Derive from the default one and merge it. (Static + declaration of method support case.) + + + + In the case of dynamic declaration of method support, another call is required instead of + the merge: RegisterGenerator. CompareGenerator + illustrates this. + + + The last step is to instruct NHibernate to use this extended registry. It can be achieved + through xml configuration under + session-factory node, or by + code before building the session factory. + Use one of them. + + YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName]]> + (); +// And build the session factory with this configuration.]]> + + Now the following query could be executed, without failing if no Max cat + exists. + + () + .Where(c => c.Name == "Max") + .Select(c => c.BirthDate.AsNullable()) + .Min();]]> + + (Of course, the same result could be obtained with (DateTime?)(c.BirthDate).) + + + + \ No newline at end of file diff --git a/doc/reference/modules/query_queryover.xml b/doc/reference/modules/query_queryover.xml index 70725c8af72..7c3bec1cfdd 100644 --- a/doc/reference/modules/query_queryover.xml +++ b/doc/reference/modules/query_queryover.xml @@ -27,7 +27,7 @@ Note: QueryOver is intended to remove the references to 'magic strings' from the ICriteria API while maintaining it's opaqueness. It is not a LINQ provider; - NHibernate has a built-in Linq provider for this. + NHibernate has a built-in Linq provider for this.