#c# #session #fluent-nhibernate #lazy-loading
#c# #сеанс #свободно-nhibernate #отложенная загрузка
Вопрос:
Привет, я свободно использую NHibernate, и меня немного смущает отложенная загрузка.
- Я запросил объект в базе данных
- измененные свойства объекта
- обновите базу данных с помощью этого объекта
Вот код:
public class Credentials
{
public virtual int Id { get; set; }
public virtual string Nick { get; set; }
public virtual string Password { get; set; }
}
public class CredentialsMap : ClassMap<Credentials>
{
public CredentialsMap()
{
Id(x => x.Id);
Map(x => x.Nick).Column("NICK");
Map(x => x.Password).Column("PASSWORD");
Table("R_CREDENTAILS");
}
}
public class Status
{
public virtual int Id { get; set; }
public virtual string Msg { get; set; }
public virtual DateTime AddTime { get; set; }
}
public class StatusMap : ClassMap<Status>
{
public StatusMap()
{
Id(x => x.Id);
Map(x => x.Msg).Column("MESSAGE");
Map(x => x.AddTime).Column("ADD_TIME");
Table("R_STATUS");
}
}
public class Account
{
public virtual int Id { get; set; }
public virtual string SelfNick { get; set; }
public virtual Credentials Credentials { get; set; }
public virtual Status Status { get; set; }
}
public class AccountMap : ClassMap<Account>
{
public AccountMap()
{
Id(x => x.Id);
Map(x => x.SelfNick).Column("SELF_NICK");
References(x => x.Credentials)
.Column("CREDENTIALS_ID")
.ForeignKey();
References(x => x.Status)
.Column("STATUS_ID")
.ForeignKey();
Table("R_ACCOUNTS");
}
}
Класс конфигурации NHibernate:
public class NHiberanteHelper
{
private static ISessionFactory _sessionFactory;
private static ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
InitializeSessionFactory();
return _sessionFactory;
}
}
private static void InitializeSessionFactory()
{
_sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008
.ConnectionString(
@"Server=TESTSQLEXPRESS;Database=SimpleNHibernate;Trusted_Connection=True;").
ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Account>().Conventions.Add( DefaultCascade.All()))
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(true, true))
.BuildSessionFactory();
}
public static ISession OpenSession()
{
return SessionFactory.OpenSession();
}
}
Вот использование:
public class LoginDbHelper
{
public static Account GetAccount(string nick)
{
using (var session = NHiberanteHelper.OpenSession())
{
var account = (session.QueryOver<Account>()
.JoinQueryOver<Credentials>(a => a.Credentials)
.Where(c => c.Nick == nick));
if (account != null)
return account.SingleOrDefault();
return null;
}
}
public static void SaveOrUpdateAccount(Account account)
{
using (var session = NHiberanteHelper.OpenSession())
{
using (var trans = session.BeginTransaction())
{
session.SaveOrUpdate(account);
trans.Commit();
}
}
}
}
Проблемный код:
var actualAccount = LoginDbHelper.GetAccount(nick);
//modify
actualAccount.Status.Msg = "New status 2";
actualAccount.Status.AddTime = DateTime.Now;
LoginDbHelper.SaveOrUpdateAccount(account);
Я получаю эту ошибку:
{"Initializing[NHibernateSample1.Status#1]-Could not initialize proxy - no Session."}
Трассировка стека:
at NHibernate.Proxy.AbstractLazyInitializer.Initialize() in d:CSharpNHNHnhibernatesrcNHibernateProxyAbstractLazyInitializer.cs:line 113
at NHibernate.Proxy.AbstractLazyInitializer.GetImplementation() in d:CSharpNHNHnhibernatesrcNHibernateProxyAbstractLazyInitializer.cs:line 191
at NHibernate.ByteCode.Castle.LazyInitializer.Intercept(IInvocation invocation) in d:CSharpNHNHnhibernatesrcNHibernate.ByteCode.CastleLazyInitializer.cs:line 61
at Castle.DynamicProxy.AbstractInvocation.Proceed()
at Castle.Proxies.StatusProxy.set_Msg(String value)
at NHibernateSample1.Program.Main(String[] args) in E:C# PROJECTSSamplesSimpleNHibernateClientNHibernateSample1Program.cs:line 215
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
Я гуглю это и думаю, что это вызвано отложенной загрузкой, потому что в методе getAccount я закрываю СЕАНС.
Это моя первая попытка с NHibernate, так КАК ЖЕ ПРАВИЛЬНО РЕШИТЬ ЭТУ ПРОБЛЕМУ?
Можно отключить ОТЛОЖЕННУЮ ЗАГРУЗКУ, если ДА, как это сделать?
Ответ №1:
Вы правы. Поскольку сеанс NHibernate закрыт в вашем методе getAccount (он открыт только в области действия using
инструкции), вы не можете загружать дополнительные объекты вне этого метода. Есть 2 потенциальных исправления:
- Создайте сеанс на уровне операции (т. Е. В методе, содержащем код проблемы), затем используйте этот сеанс в методах получения и сохранения. Вы можете использовать сеанс, передав его в качестве параметра методам.
- Измените объект, чтобы не использовать отложенную загрузку. Вы можете сделать это, добавив
.Not.LazyLoad()
кStatus
объекту в вашем fluent mapping .
Ответ №2:
Я считаю, что самый простой способ отключить отложенную загрузку — добавить соглашение DefaultLazy, т.е.:
.Conventions.Add( DefaultCascade.All(), DefaultLazy.Never() )
Обратите внимание, что включение отложенной загрузки (DefaultLazy.Always()) действительно может повысить производительность, в зависимости от вашего приложения.
Недостатком является то, что вам всегда нужно открыть сеанс, прежде чем вы сможете лениво загружать остальные данные в объект. По моему опыту, управление сеансами для поддержки отложенной загрузки — одна из самых больших проблем с NHibernate.
Комментарии:
1. Я чувствую, что big PITA
Session management to support lazy loading is one of the big pain points with NHibernate, in my experience.
полностью согласен с вами. ощущение, почему я выбрал nhibernate, должно было пойти с EF2. EF был довольно незрелым, когда мы начинали наш проект. В основном мы использовали Fluent Nhibernate, потому что он выполнял автоматическое сопоставление схемы — в то время больше ничего не делалось. Даже с учетом проблем с управлением сеансами, это все равно был чистый выигрыш, IMO.
Ответ №3:
Вы открываете и закрываете сеанс в LoginDbHelper.Метод getAccount(…) .
Попробуйте создать и открыть сеанс вне метода и передать его как параметр метода, например:
// method
public static Account GetAccount(string nick, ISession session)
{
var account = (session.QueryOver<Account>().JoinQueryOver<Credentials>(a => a.Credentials).Where(c => c.Nick == nick));
if (account != null)
return account.SingleOrDefault();
return null;
}
// usage
var actualAccount = LoginDbHelper.GetAccount(nick);
actualAccount.Status.AddTime = DateTime.Now;
using (var session = NHiberanteHelper.OpenSession())
LoginDbHelper.SaveOrUpdateAccount(account, session);
Комментарии:
1. И, да, пункт решения № 2 от Райана Гросса также применим. Более того, вероятно, в вашем случае лучше просто отключить отложенную загрузку.
Ответ №4:
В качестве обходного пути вы можете заменить этот код:
if (account != null)
return account.SingleOrDefault();
С помощью этого кода:
if (account != null)
{
var returnValue = account.SingleOrDefault();
if (returnValue != null)
{
returnValue.Status.Msg = returnValue.Status.Msg;//Cause to load the lazy object now
}
return returnValue;
}
Ответ №5:
Это также может произойти, если ваш сеанс полностью открыт, но вы позвонили Session.Clear()
. Не делайте этого.
Ответ №6:
Если вы используете это в сочетании с ASP.Net MVC или ASPCORE, проблема может быть связана с тем, что сериализатор Json пытается извлечь данные из IEnumerable, которые вы получаете через свой сеанс. Поскольку сериализатор выполняется после выхода из вашего метода действий, а ваш контроллер удален (вместе с сеансом NH), он не может получить доступ к навигационным свойствам, загруженным с отложенной загрузкой.
Простое решение — добавить .ToList() или .toArray() для вашего запроса LINQ в методе action .