我的任务是直接从数据库中提取DTO。作为ORM,我使用NHibernate和LINQ作为查询的手段。下面是我的域类和一个DTO类,我用它来将数据返回到客户端(类被简化为只包含一些属性来证明这一点)。
public class DocLanguage
{
public Guid Id { get; set; }
public string Name { get; set; }
public string PublicCode { get; set; }
}
public class Document
{
public Guid Id { get; set; }
}
public class OutgoingDocument: Document
{
public DocLanguage DocLanguage { get; set; }
}
public class OutgoingDocumentDto
{
public Guid Id { get; set; }
public Guid DocLanguageId { get; set; }
public string DocLanguageName { get; set; }
}
这是我用来从数据库加载DTO的查询。
IQueryable<OutgoingDocument> documents = GetQueryable();
var query = from doc in documents
select new OutgoingDocumentDto
{
Id = doc.Id,
DocLanguageId = doc.DocLanguage.Id,
DocLanguageName = doc.DocLanguage.Name
}
var documentList = query.ToList();
它生成以下SQL。
exec sp_executesql N'select
outgoingdo0_.documentId as col_0_0_,
doclanguag1_.Id as col_1_0_,
doclanguag1_.name as col_2_0_
from OutgoingDocuments outgoingdo0_
inner join Documents outgoingdo0_1_
on outgoingdo0_.documentId=outgoingdo0_1_.Id
left outer join DicDocLanguages doclanguag1_
on outgoingdo0_1_.docLanguageId=doclanguag1_.Id'
在NULL
字段中没有docLanguageId
值(这不是必需的)之前,它很有效。在另一种情况下,它会引发异常:
NHibernate.Exceptions.GenericADOException was unhandled by user code
Message=Could not execute query[SQL: SQL not available]
Source=NHibernate
SqlString=SQL not available
StackTrace:
at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 653
at NHibernate.Impl.AbstractSessionImpl.List(IQueryExpression queryExpression, QueryParameters parameters) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\AbstractSessionImpl.cs:line 92
at NHibernate.Impl.ExpressionQueryImpl.List() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\ExpressionQueryImpl.cs:line 61
at NHibernate.Linq.DefaultQueryProvider.ExecuteQuery(NhLinqExpression nhLinqExpression, IQuery query, NhLinqExpression nhQuery) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Linq\DefaultQueryProvider.cs:line 103
at NHibernate.Linq.DefaultQueryProvider.Execute(Expression expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Linq\DefaultQueryProvider.cs:line 35
at NHibernate.Linq.DefaultQueryProvider.Execute[TResult](Expression expression) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Linq\DefaultQueryProvider.cs:line 40
at Remotion.Linq.QueryableBase`1.GetEnumerator() in :line 0
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
InnerException: System.Reflection.TargetInvocationException
HResult=-2146232828
Message=Exception has been thrown by the target of an invocation.
Source=mscorlib
StackTrace:
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Delegate.DynamicInvokeImpl(Object[] args)
at System.Delegate.DynamicInvoke(Object[] args)
at NHibernate.Linq.ResultTransformer.TransformTuple(Object[] tuple, String[] aliases) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Linq\ResultTransformer.cs:line 25
at NHibernate.Hql.HolderInstantiator.Instantiate(Object[] row) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Hql\HolderInstantiator.cs:line 80
at NHibernate.Hql.Ast.ANTLR.Loader.QueryLoader.GetResultList(IList results, IResultTransformer resultTransformer) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Hql\Ast\ANTLR\Loader\QueryLoader.cs:line 302
at NHibernate.Loader.Loader.ListIgnoreQueryCache(ISessionImplementor session, QueryParameters queryParameters) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 1497
at NHibernate.Loader.Loader.List(ISessionImplementor session, QueryParameters queryParameters, ISet`1 querySpaces, IType[] resultTypes) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Loader\Loader.cs:line 1491
at NHibernate.Hql.Ast.ANTLR.Loader.QueryLoader.List(ISessionImplementor session, QueryParameters queryParameters) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Hql\Ast\ANTLR\Loader\QueryLoader.cs:line 288
at NHibernate.Hql.Ast.ANTLR.QueryTranslatorImpl.List(ISessionImplementor session, QueryParameters queryParameters) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Hql\Ast\ANTLR\QueryTranslatorImpl.cs:line 112
at NHibernate.Engine.Query.HQLQueryPlan.PerformList(QueryParameters queryParameters, ISessionImplementor session, IList results) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Query\HQLQueryPlan.cs:line 105
at NHibernate.Impl.SessionImpl.List(IQueryExpression queryExpression, QueryParameters queryParameters, IList results) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs:line 643
InnerException: System.NullReferenceException
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=Anonymously Hosted DynamicMethods Assembly
StackTrace:
at lambda_method(Closure , Object[] )
InnerException:
我尝试使用以下代码显式检查nullity并为DTO属性提供默认值:
var query = from doc in documents
select new OutgoingDocumentDto
{
Id = doc.Id,
DocLanguageId = doc.DocLanguage == null ?
Guid.Empty :
doc.DocLanguage.Id,
DocLanguageName = doc.DocLanguage == null ?
Guid.Empty :
doc.DocLanguage.Name
}
导致这个SQL。
exec sp_executesql N'select
outgoingdo0_.documentId as col_0_0_,
doclanguag1_.Id as col_1_0_,
doclanguag1_.Id as col_2_0_,
doclanguag1_.Id as col_3_0_,
doclanguag1_.name as col_4_0_,
doclanguag1_.Id as Id40_0_,
doclanguag1_.Id as Id40_1_,
doclanguag1_.name as nameRU40_0_,
doclanguag1_.publicCode as publicCode40_0_,
doclanguag1_.name as nameRU40_1_,
doclanguag1_.publicCode as publicCode40_1_
from OutgoingDocuments
outgoingdo0_ inner join Documents outgoingdo0_1_
on outgoingdo0_.documentId=outgoingdo0_1_.Id
left outer join DicDocLanguages doclanguag1_
on outgoingdo0_1_.docLanguageId=doclanguag1_.Id'
似乎NHibernate LINQ提供程序直接将LINQ查询中的所有成员访问表达式转换为SQL查询,从而产生重复的字段选择。实际上,它可以工作,也许优化器使得这个查询过程和前一个一样快。但它很丑陋,随着涉及更多的财产而变得更加丑陋。
我认为这个任务很常见,我想知道是否存在使用默认值在select子句中提供属性的正确方法。
答案 0 :(得分:2)
这可能不是最好的解决方案,但我找到了blog post,它向您展示了如何让NHibernate识别和翻译Linq查询中使用的自定义扩展方法,该示例专门处理“合并” “操作。解决方案是创建一个Coalesce()扩展方法,然后通过为方法指定一个自定义HQL生成器,用NHibernate“注册”它,然后使用默认配置中指定的自定义注册表生成器将它们添加到NHibernate的“知识库”中HQL生成。
理论上,您可以为任何可以转换为目标模式的有效SQL的扩展方法执行此操作;您甚至可以使用它来使NHibernate调用您放在数据库中的自定义SQL函数(但我离题了)。
答案 1 :(得分:1)
您可以在DTO级别解决此问题。 您可以将数据库对象绑定到内部可空字段,并将其用作公共属性的后备字段,您可以在其中放置默认值逻辑。像这样:
internal virtual DocLanguage docLanguage {get;set;}
public int DocLanguageName
{
get{ return docLanguage == null ? Guid.Empty : docLanguage.Name; }
}