How can I have NHibernate only generate the SQL without executing it? - sql

I know how to log the SQL to log4net/NLog/trace window at runtime with the show_sql configuration option.
What I'm looking for is a way to give a Query<T>() to NHibernate retrieve the generated SQL.
I've looked through the Persister class, the Drivers, different Interceptors and Events. There are so many places to look, even narrowing down my search would be of great help.

You can get the generated sql queries without execution with the following methods:
For the NHibernate.Linq queries:
public String GetGeneratedSql(System.Linq.IQueryable queryable, ISession session)
{
var sessionImp = (ISessionImplementor) session;
var nhLinqExpression = new NhLinqExpression(queryable.Expression, sessionImp.Factory);
var translatorFactory = new ASTQueryTranslatorFactory();
var translators = translatorFactory.CreateQueryTranslators(nhLinqExpression, null, false, sessionImp.EnabledFilters, sessionImp.Factory);
return translators[0].SQLString;
}
For Criteria queries:
public String GetGeneratedSql(ICriteria criteria)
{
var criteriaImpl = (CriteriaImpl) criteria;
var sessionImpl = (SessionImpl) criteriaImpl.Session;
var factory = (SessionFactoryImpl) sessionImpl.SessionFactory;
var implementors = factory.GetImplementors(criteriaImpl.EntityOrClassName);
var loader = new CriteriaLoader((IOuterJoinLoadable) factory.GetEntityPersister(implementors[0]), factory, criteriaImpl, implementors[0], sessionImpl.EnabledFilters);
return loader.SqlString.ToString();
}
For QueryOver queries:
public String GetGeneratedSql(IQueryOver queryOver)
{
return GetGeneratedSql(queryOver.UnderlyingCriteria);
}
For Hql queries:
public String GetGeneratedSql(IQuery query, ISession session)
{
var sessionImp = (ISessionImplementor)session;
var translatorFactory = new ASTQueryTranslatorFactory();
var translators = translatorFactory.CreateQueryTranslators(query.QueryString, null, false, sessionImp.EnabledFilters, sessionImp.Factory);
return translators[0].SQLString;
}

For NHibernate 5.2 in case you want to see actual DbCommand prepared for query (so you can check both SQL in cmd.CommandText and supplied parameters in cmd.Parameters):
//For LINQ
public IEnumerable<DbCommand> GetDbCommands<T>(IQueryable<T> query, ISession s)
{
return GetDbCommands(LinqBatchItem.Create(query), s);
}
//For HQL
public IEnumerable<DbCommand> GetDbCommands(IQuery query, ISession s)
{
return GetDbCommands(new QueryBatchItem<object>(query), s);
}
//For QueryOver
public IEnumerable<DbCommand> GetDbCommands(IQueryOver query, ISession s)
{
return GetDbCommands(query.RootCriteria, s);
}
//For Criteria (needs to be called for root criteria)
public IEnumerable<DbCommand> GetDbCommands(ICriteria rootCriteria, ISession s)
{
return GetDbCommands(new CriteriaBatchItem<object>(query), s);
}
//Adapted from Loader.PrepareQueryCommand
private static IEnumerable<DbCommand> GetDbCommands(IQueryBatchItem item, ISession s)
{
var si = s.GetSessionImplementation();
item.Init(si);
var commands = item.GetCommands();
foreach (var sqlCommand in commands)
{
//If you don't need fully prepared command sqlCommand.Query contains SQL returned by accepted answer
var sqlString = sqlCommand.Query;
sqlCommand.ResetParametersIndexesForTheCommand(0);
var command = si.Batcher.PrepareQueryCommand(System.Data.CommandType.Text, sqlString, sqlCommand.ParameterTypes);
RowSelection selection = sqlCommand.QueryParameters.RowSelection;
if (selection != null && selection.Timeout != RowSelection.NoValue)
{
command.CommandTimeout = selection.Timeout;
}
sqlCommand.Bind(command, si);
IDriver driver = si.Factory.ConnectionProvider.Driver;
driver.RemoveUnusedCommandParameters(command, sqlString);
driver.ExpandQueryParameters(command, sqlString, sqlCommand.ParameterTypes);
yield return command;
}
}

Based on the NHibernate version 3.4 the method for linq expression is:
public String GetGeneratedSql(System.Linq.IQueryable queryable, ISession session)
{
var sessionImp = (ISessionImplementor)session;
var nhLinqExpression = new NhLinqExpression(queryable.Expression,
sessionImp.Factory);
var translatorFactory = new ASTQueryTranslatorFactory();
var translators = translatorFactory.CreateQueryTranslators(nhLinqExpression.Key, nhLinqExpression, null, false,
sessionImp.EnabledFilters, sessionImp.Factory);
var sql = translators.First().SQLString;
var formamttedSql = FormatStyle.Basic.Formatter.Format(sql);
int i = 0;
var map = ExpressionParameterVisitor.Visit(queryable.Expression, sessionImp.Factory).ToArray();
formamttedSql = Regex.Replace(formamttedSql, #"\?", m => map[i++].Key.ToString().Replace('"', '\''));
return formamttedSql;
}

Here is how to get generated Sql from Hql with NH 5.2 (a breaking change in NH 4.0.4 appeared which makes the Hql part of the top voted solution obsolete):
public string HqlToSql(string hql, ISession session)
{
var sessionImp = (ISessionImplementor)session;
var translatorFactory = new ASTQueryTranslatorFactory();
var translators = translatorFactory.CreateQueryTranslators(new NHibernate.Hql.StringQueryExpression(hql),
null, false, sessionImp.EnabledFilters, sessionImp.Factory);
var hqlSqlGenerator = new HqlSqlGenerator(((QueryTranslatorImpl)translators[0]).SqlAST, sessionImp.Factory);
hqlSqlGenerator.Generate();
return hqlSqlGenerator.Sql.ToString();
}

Related

Is there a way to improve the code? .Net Core 6.0 and Cosmos Database with SQL API

It's my first time using .Net Core 6.0 WebAPI and Cosmos Database with SQL API, so I'm concerned that I've done things correctly.
I have used /address/zipcode as a partition key in the below sample
public class FamilyService : IFamilyService
{
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public Task<Family> GetFamilyDataAsync(string id, string zipcode)
{
return Task.FromResult(_container.GetItemLinqQueryable<Family>(allowSynchronousQueryExecution: true)
.Where(p => p.Id == id && p.Address.ZipCode == zipcode)
.ToList().First());
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipcode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipcode));
}
private async Task<bool> GetFamilyDataFromId(string id, string zipcode)
{
string query = $"select * from c where c.id=#familyId";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#familyId", id);
List<Family> familyResults = new List<Family>();
FeedIterator streamResultSet = _container.GetItemQueryStreamIterator(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipcode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (streamResultSet.HasMoreResults)
{
using (ResponseMessage responseMessage = await streamResultSet.ReadNextAsync())
{
if (responseMessage.IsSuccessStatusCode)
{
dynamic streamResponse = FromStream<dynamic>(responseMessage.Content);
List<Family> familyResult = streamResponse.Documents.ToObject<List<Family>>();
familyResults.AddRange(familyResult);
}
else
{
return false;
}
}
}
if (familyResults != null && familyResults.Count > 0)
{
return true;
}
return familyResults;
}
Especially I want to focus more on those methods that retrieves the data from the database.
Update#1 : I have updated the code as suggested by #Mark Brown.
public class FamilyService : IFamilyService
{
private Container _container;
private static readonly JsonSerializer Serializer = new JsonSerializer();
public FamilyService(CosmosClient dbClient, string databaseName, string containerName)
{
this._container = dbClient.GetContainer(databaseName, containerName);
}
public async Task<Family> GetFamilyDataAsync(string id, string zipCode)
{
return await _container.ReadItemAsync<Family>(id, new PartitionKey(zipCode));
}
public async Task<List<Family>> GetAllFamilyDataAsync(string zipCode)
{
string query = $"SELECT * FROM Family";
QueryDefinition queryDefinition = new QueryDefinition(query);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataByLastNameAsync(string lastName, string zipCode)
{
string query = $"SELECT * FROM Family where Family.lastName = #lastName";
QueryDefinition queryDefinition = new QueryDefinition(query).WithParameter("#lastName", lastName);
List<Family> familyResults = new List<Family>();
FeedIterator<Family> feed = _container.GetItemQueryIterator<Family>(
queryDefinition,
requestOptions: new QueryRequestOptions()
{
PartitionKey = new PartitionKey(zipCode),
MaxItemCount = 10,
MaxConcurrency = 1
});
while (feed.HasMoreResults)
{
FeedResponse<Family> response = await feed.ReadNextAsync();
foreach (Family item in response)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task<List<Family>> GetAllFamilyDataPaginatedAsync(int pageNumber, string zipCode)
{
int pageSize = 2;
int itemsToSkip = (pageSize * pageNumber) - pageSize;
QueryRequestOptions options = new QueryRequestOptions { MaxItemCount = pageSize };
string continuationToken = null;
List<Family> familyResults = new List<Family>();
IQueryable<Family> query = this._container
.GetItemLinqQueryable<Family>(false, continuationToken, options)
.Where(i => i.Address.ZipCode == zipCode)
.Skip(itemsToSkip);
using (FeedIterator<Family> iterator = query.ToFeedIterator<Family>())
{
FeedResponse<Family> feedResponse = await iterator.ReadNextAsync();
foreach (Family item in feedResponse)
{
familyResults.Add(item);
}
}
return familyResults;
}
public async Task AddFamilyDataAsync(Family family)
{
await this._container.CreateItemAsync<Family>(family, new PartitionKey(family.Address.ZipCode));
}
public async Task UpdateFamilyDataAsync(Family family)
{
await this._container.ReplaceItemAsync<Family>(family, family.Id, new PartitionKey(family.Address.ZipCode));
}
public async Task DeleteFamilyDataAsync(string id, string zipCode)
{
await this._container.DeleteItemAsync<Family>(id, new PartitionKey(zipCode));
}
}
If you are searching for something with the partition key and id then that can only return a single item as id must be unique within a pk. So GetFamilyDataAsync() can be implemented using ReadItemAsync() rather than a query. PS: Not sure why you would ever do this, allowSynchronousQueryExecution: true
GetFamilyDataFromId() also can be implemented using ReadItemAsync(). Even if this did require a query, it doesn't make sense to implement to return a stream, then deserialize into an object. Just use the variant that can deserialize directly into a POCO.
Can't say if this is completely optimal for everything, but you can go here to find more things as a place to start, https://learn.microsoft.com/en-us/azure/cosmos-db/sql/best-practice-dotnet
The other thing to call out is zip code as a partition key. This might work at small scale, but it could run into issues at larger scale depending on how many families and how many records for each family are stored within a single partition. Max size for a single partition key value is 20 GB. Fine for small workloads but possibly an issue at larger scale.

ASP.NET CORE using ADO.NET with AutoMapper

What is the proper way of using AutoMapper with ADO.NET in ASP.NET Core in generic way?
Also the SQL query has the same column names as in class of <T>
In specified example variable result is always empty list, so automapper could not map object properties to DbDataReader columns.
public class CustomDbContext : BaseRepository
{
readonly DbConnection dbConn;
public CustomDbContext(RepoDbContext context) : base(context)
{
dbConn = context.Database.GetDbConnection();
}
public async Task<List<T>> Get<T>(string sql) where T : class
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<DbDataReader, List<T>>();
});
var mapper = config.CreateMapper();
await dbConn.OpenAsync();
using (var command = dbConn.CreateCommand())
{
command.CommandText = sql;
var reader = await command.ExecuteReaderAsync();
var result = new List<T>();
if (reader.HasRows)
{
await reader.ReadAsync();
result = mapper.Map<DbDataReader, List<T>>(reader);
}
reader.Dispose();
return result;
}
}
}
Should I specify more detailed AutoMapper configuration or it can't be done this way?
Try using interfaces as IDataReader and IEnumerable instead of classes DbDataReader and List.
public async Task<List<T>> Get<T>(string sql) where T : class
{
var config = new AutoMapper.MapperConfiguration(cfg =>
{
cfg.CreateMap<IDataReader, IEnumerable<T>>();
});
var mapper = config.CreateMapper();
await dbConn.OpenAsync();
using (var command = dbConn.CreateCommand())
{
command.CommandText = sql;
var reader = await command.ExecuteReaderAsync();
var result = new List<T>();
if (reader.HasRows)
{
await reader.ReadAsync();
result = mapper.Map<IDataReader, IEnumerable<T>>(reader).ToList();
}
reader.Dispose();
return result;
}
}

Error on Rendering a View to a String

I am trying to render a view to a string in ASP Net Core 1 but keep receiving the error:
Operator '!' cannot be applied to operand of type ''
on this line:
view.RenderAsync(viewContext).GetAwaiter().GetResult();
I am confused as to why this might be the case and looking for some assistance.
Alot of the examples and help I have seen in this area relate to RC1. We are using the final core 1.0 here.
I'm using the following code which i found online:
public ViewRender(
IRazorViewEngine viewEngine,
ITempDataProvider tempDataProvider,
IServiceProvider serviceProvider)
{
_viewEngine = viewEngine;
_tempDataProvider = tempDataProvider;
_serviceProvider = serviceProvider;
}
public string Render<TModel>(string name, TModel model, ActionContext actionContext)
{
var viewEngineResult = _viewEngine.FindView(actionContext, name, true);
if (!viewEngineResult.Success)
{
throw new InvalidOperationException(string.Format("Couldn't find view '{0}'", name));
}
var view = viewEngineResult.View;
using (var output = new StringWriter())
{
var viewContext = new ViewContext(
actionContext,
view,
new ViewDataDictionary<TModel>(
metadataProvider: new EmptyModelMetadataProvider(),
modelState: new ModelStateDictionary())
{
Model = model
},
new TempDataDictionary(
actionContext.HttpContext,
_tempDataProvider),
output,
new HtmlHelperOptions());
view.RenderAsync(viewContext).GetAwaiter().GetResult();
return output.ToString();
}
}
}

NHibernate Criterion Problem

I have a query in hibernate that looks like:
string[] NewsId = new string[] {"ABC","DEF","GHI"};
public IEnumerable<News> NewsSelected(string[] NewsId)
{
List<ICriterion> criteria = new List<ICriterion>();
criteria.Add(Restrictions.In("Id", NewsId));
var results = _repository.GetByCriteria(criteria);
return results;
}
It returns zero, but
public IEnumerable<News> NewsSelected(string[] NewsId)
{
List<ICriterion> criteria = new List<ICriterion>();
criteria.Add(Restrictions.In("Id", new[]{"ABC","DEF","GHI"}));
var results = _repository.GetByCriteria(criteria);
return results;
}
works perfectly. What am I missing here?
You need copy query parameters to a local variable like that:
string[] NewsId = new string[] {"ABC","DEF","GHI"};
public IEnumerable<News> NewsSelected(string[] NewsId)
{
var newsId = NewsId;
List<ICriterion> criteria = new List<ICriterion>();
criteria.Add(Restrictions.In("Id", newsId));
var results = _repository.GetByCriteria(criteria);
return results;
}

Sql server FileStream type in nhibernate

I would like to use FileStream with Nhibernate.
The only post I found on stackoverflow is Sql 2008 Filestream with NHibernate
In the meantime, Nhibernate 3.0 (and 3.1) was released.
Does someone know a solution ?
It is not planned to be supported by nhibernate. (WONT FIX)
http://groups.google.com/group/nhusers/browse_thread/thread/e4a8f038f9c40a0d/a63ac4a8ce4f1244?pli=1
I hope it will change in the future ... maybe an extension (the open source magic)
Nhibernate can be used with the FILESTREAM... sort of.
Persist the object using Nhibernate as normal. You can then issue a couple of standard SQL Queries to take care of the filestream. I place the queries as named queries in my mapping file for the object.
public void Save(Photo photo){
//Save photo data
session.Save(photo);
//get path
String path;
Byte[] context;
IQuery qry1 = session.GetNamedQuery(QUERY_SET_BLANK);
qry1.SetInt64("photoId", photo.RID);
int cnt = qry1.ExecuteUpdate();
IQuery qry2 = session.GetNamedQuery(QUERY_GET_PATH);
qry2.SetInt64("photoId", photo.RID);
System.Collections.IList results = qry2.List();
object[] item = (object[]) results[0];
path = (String) item[0];
context = (Byte[])item[1];
if (context == null) throw new QueryException("Possible null transaction");
//save photo
using (SqlFileStream sqlFile = new SqlFileStream(path, context, FileAccess.Write)) {
photo.Image.Save(sqlFile, ImageFormat.Jpeg);
sqlFile.Close();
}
}
public Photo Get(Int64 rid) {
Photo result = session.Get<Photo>(rid);
if (result != null) {
IQuery qry = session.GetNamedQuery(QUERY_GET_PATH);
qry.SetInt64("photoId", result.RID);
System.Collections.IList results = qry.List();
object[] item = (object[])results[0];
var path = (String)item[0];
var context = (Byte[])item[1];
if (context == null) throw new QueryException("Possible null transaction");
using (SqlFileStream sqlFile = new SqlFileStream(path, context, FileAccess.Read))
result.Image = new System.Drawing.Bitmap(System.Drawing.Bitmap.FromStream(sqlFile));
}
return result;
}
Where Photo is an object mapped to a table. The actual column of the FILESTREAM type is not mapped to the object (excluded from mapping), the the named queries take care of the persistence for that column.
The name queries look like:
<sql-query name="PhotoPathContext">
select Photo.PathName() as path, GET_FILESTREAM_TRANSACTION_CONTEXT() as con
from Core.Photos where PhotoRID = :photoId
</sql-query>
<sql-query name="PhotoSetBlankFileStream">
update Core.Photos set Photo = Cast('' as varbinary(max)) where PhotoRID = :photoId
</sql-query>
The thing need to be wrapped in a transaction which is required by the GET_FILESTRAM_TRANSACTION_CONTEXT() method in the query.
Unit tests would look something like:
[Test]
public void TestSavePhoto() {
IList<Model.Photo> photos = repo.GetPhotos();
VegTabUtilityServices.Photo photo = new VegTabUtilityServices.Photo();
VegTabUtil.Model.Photo ph = photos[0];
photo.RowGuid = ph.GetGuid().Value;
photo.Name = ph.Name ?? photo.RowGuid.ToString();
photo.Image = ph.Image;
ISession session = SessionManager.Instance.Session;
PhotoService ps = new PhotoService(session);
using (NHibernate.ITransaction tx = session.BeginTransaction()) {
ps.Save(photo);
tx.Commit();
}
Assert.Greater(photo.RID, 0);
}
[Test]
public void TestPhotoConnection() {
ISession session = SessionManager.Instance.Session;
PhotoService ps = new PhotoService(session);
Photo p;
using (NHibernate.ITransaction tx = session.BeginTransaction()) {
p = ps.Get(6l);
tx.Commit();
}
Assert.NotNull(p);
Assert.NotNull(p.Image);
logger.Debug(String.Format("{0} by {1} pixels", p.Image.Width, p.Image.Height));
}
I have a more complete article on codeproject