SOAP и NHibernate Сессия в C # - PullRequest
1 голос
/ 23 апреля 2010

В наборе веб-сервисов SOAP пользователь аутентифицируется с помощью пользовательского заголовка SOAP (имя пользователя / пароль). Каждый раз, когда пользователь вызывает WS, вызывается следующий метод Auth для аутентификации и извлечения объекта User из сеанса NHibernate:

[...]
public Services : Base {
    private User user;

    [...]
    public string myWS(string username, string password) 
    {
        if( Auth(username, password) ) { [...] }
    }
}

public Base : WebService {

   protected static ISessionFactory sesFactory;
   protected static ISession session;

   static Base {
     Configuration conf = new Configuration();
            [...]
     sesFactory = conf.BuildSessionFactory();
   }

    private bool Auth(...) {    

        session = sesFactory.OpenSession();

        MembershipUser user = null;
            if (UserCredentials != null && Membership.ValidateUser(username, password)) 
            {
             luser = Membership.GetUser(username);
            }
        ...
        try {
      user = (User)session.Get(typeof(User), luser.ProviderUserKey.ToString()); 
 } catch {
      user = null;
             throw new [...]
        }

        return user != null; 
     }

}

Когда работа WS завершена, сессия очищается и все работает: WS создают, изменяют и изменяют объекты, а Nhibernate сохраняет их в БД.

Проблемы возникают, когда пользователь (одно имя пользователя / пароль) одновременно вызывает один и тот же WS с разных клиентов (машин). Состояние сохраненных объектов несовместимо.

Как правильно управлять сессией, чтобы избежать этого? Я искал, и документация по управлению сессиями в NHibernate действительно обширна. Должен ли я заблокировать пользовательский объект? Должен ли я настроить управление «общим сеансом» между вызовами WS от одного и того же пользователя? Должен ли я использовать транзакцию каким-нибудь хитрым способом?

Спасибо

Update1

Да, mSession - это «сессия».

Update2

Даже с нестатическим объектом сеанса данные, сохраненные в БД, являются противоречивыми. Шаблон, который я использую для вставки / сохранения объекта, выглядит следующим образом:

try {
   Auth([...]);
} catch {
  // ....
}
var return_value = [...];
try {
  using(ITransaction tx = session.Transaction)
  {
    tx.Begin();

    MyType obj = new MyType();
    user.field = user.field - obj.field; // The fields names are i.e. but this is actually what happens.

    session.Save(user);
    session.Save(obj);

    tx.Commit();

    return_value = obj.another_field;
  }
} catch ([...]) {
    // Handling exceptions...   
} finally {
    // Clean up
    session.Flush();
    session.Close();
}

return return_value;

Все новые объекты (MyType) правильно сохранены, но статус user.field не такой, как я ожидал. Даже поле obj.another_field является правильным (поле является идентификатором с сгенерированной политикой = при сохранении).

Как будто 'user.field = user.field - obj.field;' выполняется больше раз, чем необходимо.

Ответы [ 2 ]

2 голосов
/ 26 апреля 2010

Действительно странно, что поведение выглядит так, как будто оно выполняется БОЛЬШЕ раз, чем оно было на самом деле.Я ожидал бы обратного.Но это не проблема, давайте добавим некоторый контроль параллелизма в ваше приложение.

У вас есть несколько вариантов управления параллелизмом между потоками, которые одновременно обращаются к вашим объектам: оптимистично и пессимистично.

Оптимистичное управление позволит вашим сеансам работать до того времени, когда они попытаются сохранить объект, а затем выдаст исключение.Поймать и обойти исключение может быть очень скучно для вашего пользователя, так как вам, вероятно, придется попросить его повторить ту же операцию, но если число случаев, когда возникают проблемы с совпадением, не имеет значения для вашего контекста, я 'выберите один из них: их влияние на общую производительность вашего приложения меньше.

С другой стороны у вас пессимистический контроль.В этом случае у вас не будет сюрпризов при сохранении объектов, но второй сеанс должен будет ждать, пока первый не завершит свою транзакцию, пока он не сможет продолжить свою работу.Если первый сеанс занимает слишком много времени, очевидно, вы получите какое-то исключение тайм-аута.Если это займет регулярное время, все будет работать, но ваша система будет иметь эту среднюю скорость, пораженную временем блокировки.


Оптимистично - Вариант 1: вы можете сделать какую-то грязную проверку, поэтому NHibernateпроверит, имеет ли объект, который обновляется, те же значения, которые он имел до изменения свойства.Любая разница приведет к исключению.Чтобы настроить его (через Fluent NH), вам нужно добавить что-то похожее на:

        OptimisticLock.Dirty().DynamicUpdate();

Включение DynamicUpdate говорит NHibernate проверять только сохраняемые свойства, а не весь объект.Вы можете использовать OptimisticLock.All (), чтобы заставить NHibernate проверять все свойства, но я думаю, что это не нужно в вашем случае.

Оптимистичный - вариант 2: другой вариант - явно указать столбец версии, который будетобрабатываются NH, чтобы узнать, является ли объект, который вы пытаетесь сохранить, наиболее обновленным.Если нет, как обычно, вы получите исключение.Чтобы настроить его с помощью FluentNH:

        OptimisticLock.Version();
        Version(x => x.Version); // supposing that you have a Version property

Пессимистично: пессимистическая блокировка достигается через перечисление LockMode в ваших методах Get.Как уже говорилось ранее, объект не будет прочитан никаким другим сеансом, пока сеанс владельца не завершит свою транзакцию.Используйте что-то вроде этого:

        MappedEntity ent1 = session1.Get<MappedEntity>(entity.Id, LockMode.Force);

Использование любого из этих подходов, вероятно, решит ваши проблемы, но рассмотрите плюсы и минусы каждого в вашем сценарии.

Вся показанная информацияздесь также доступно в блоге Ayende, с соответствующими отображениями HBM.Смотрите это: http://ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx

Надеюсь, что это поможет, и дайте мне знать, если я могу помочь вам чем-нибудь еще.

С уважением,

Filipe

0 голосов
/ 26 апреля 2010

Fellow,

Даже не видя код, который вы используете для сохранения своего объекта, не рекомендуется разделять один и тот же сеанс между разными вызовами.

Фабрика сеансов является очень дорогим объектом для создания и не выполняет никаких действий, специфичных для метода, поэтому всегда полезно распределить его между вызовами (используя статическую переменную, как вы это сделали, например). Сеанс, тем не менее, является специфическим объектом контекста вызова. Вы должны использовать один сеанс / вызов, без сомнения об этом.

Просто измените поведение вашего статического объекта сеанса и попробуйте. Если это не решит вашу проблему, пожалуйста, обновите ваш вопрос, указав ту часть кода, которая обновляет ваш объект, что позволит нам лучше попробовать.

С уважением,

Филип

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...