|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Linq;
|
|
|
using System.Threading;
|
|
|
|
|
|
namespace ZeroLevel.Services.Collections
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Класс обертывает коллекцию вида ключ-значение и позволяет проводить над ней транзакционные обновления
|
|
|
/// </summary>
|
|
|
/// <typeparam name="TKey">Тип ключа коллекции</typeparam>
|
|
|
/// <typeparam name="TValue">Тип значения коллекции</typeparam>
|
|
|
public class KeyListValueTransactCollection<TKey, TValue> :
|
|
|
ITransactable
|
|
|
{
|
|
|
/// <summary>
|
|
|
/// Коллекция
|
|
|
/// </summary>
|
|
|
readonly Dictionary<TKey, List<TValue>> _collection = new Dictionary<TKey, List<TValue>>();
|
|
|
private ReaderWriterLockSlim _rwLock =
|
|
|
new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
|
|
/// <summary>
|
|
|
/// Проверка наличия ключа
|
|
|
/// </summary>
|
|
|
/// <param name="key"></param>
|
|
|
/// <returns></returns>
|
|
|
public bool HasKey(TKey key)
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
_rwLock.EnterReadLock();
|
|
|
return _collection.ContainsKey(key);
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
_rwLock.ExitReadLock();
|
|
|
}
|
|
|
}
|
|
|
/// <summary>
|
|
|
/// Получение значения коллекции по ключу
|
|
|
/// </summary>
|
|
|
/// <param name="key">Ключ</param>
|
|
|
/// <returns>Значение</returns>
|
|
|
public IEnumerable<TValue> this[TKey key]
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
_rwLock.EnterReadLock();
|
|
|
List<TValue> value;
|
|
|
if (_collection.TryGetValue(key, out value))
|
|
|
return value;
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
_rwLock.ExitReadLock();
|
|
|
}
|
|
|
throw new KeyNotFoundException();
|
|
|
}
|
|
|
}
|
|
|
/// <summary>
|
|
|
/// Коллекция ключей
|
|
|
/// </summary>
|
|
|
public IEnumerable<TKey> Keys
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
_rwLock.EnterReadLock();
|
|
|
return _collection.Keys;
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
_rwLock.ExitReadLock();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
/// <summary>
|
|
|
/// Коллекция значений
|
|
|
/// </summary>
|
|
|
public IEnumerable<List<TValue>> Values
|
|
|
{
|
|
|
get
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
_rwLock.EnterReadLock();
|
|
|
return _collection.Values.ToArray();
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
_rwLock.ExitReadLock();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
#region Transaction update
|
|
|
/// <summary>
|
|
|
/// Список не обновленных данных (т.е. тех которые удалены в базе)
|
|
|
/// </summary>
|
|
|
readonly List<TKey> _removingDate = new List<TKey>();
|
|
|
/// <summary>
|
|
|
/// Обновленные данные
|
|
|
/// </summary>
|
|
|
readonly Dictionary<TKey, List<TValue>> _updatedRecords = new Dictionary<TKey, List<TValue>>();
|
|
|
/// <summary>
|
|
|
/// Новые данные
|
|
|
/// </summary>
|
|
|
readonly Dictionary<TKey, List<TValue>> _newRecords = new Dictionary<TKey, List<TValue>>();
|
|
|
|
|
|
void ClearTransactionDate()
|
|
|
{
|
|
|
_removingDate.Clear();
|
|
|
_updatedRecords.Clear();
|
|
|
_newRecords.Clear();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Добавление или обновления записи
|
|
|
/// </summary>
|
|
|
/// <param name="id">Идентификатор записи</param>
|
|
|
/// <param name="value">Значение</param>
|
|
|
public void Post(TKey id, TValue value)
|
|
|
{
|
|
|
if (_isUpdating.State == false)
|
|
|
{
|
|
|
throw new Exception("Method Post allowed only in transaction");
|
|
|
}
|
|
|
if (!HasKey(id))
|
|
|
{
|
|
|
if (_newRecords.ContainsKey(id) == false)
|
|
|
{
|
|
|
_newRecords.Add(id, new List<TValue>());
|
|
|
}
|
|
|
_newRecords[id].Add(value);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
if (!_updatedRecords.ContainsKey(id))
|
|
|
{
|
|
|
_updatedRecords.Add(id, new List<TValue>());
|
|
|
}
|
|
|
_updatedRecords[id].Add(value);
|
|
|
if (_removingDate.Contains(id))
|
|
|
_removingDate.Remove(id);
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
readonly AtomicBoolean _isUpdating = new AtomicBoolean();
|
|
|
|
|
|
public bool Commit()
|
|
|
{
|
|
|
if (_isUpdating.State == false) return false;
|
|
|
try
|
|
|
{
|
|
|
_rwLock.EnterWriteLock();
|
|
|
foreach (TKey id in _removingDate)
|
|
|
{
|
|
|
_collection.Remove(id);
|
|
|
}
|
|
|
foreach (TKey key in _newRecords.Keys)
|
|
|
{
|
|
|
_collection.Add(key, _newRecords[key]);
|
|
|
}
|
|
|
foreach (TKey key in _updatedRecords.Keys)
|
|
|
{
|
|
|
_collection[key] = _updatedRecords[key];
|
|
|
}
|
|
|
}
|
|
|
finally
|
|
|
{
|
|
|
_rwLock.ExitWriteLock();
|
|
|
ClearTransactionDate();
|
|
|
_isUpdating.Reset();
|
|
|
}
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public bool Rollback()
|
|
|
{
|
|
|
if (_isUpdating.State == false) return false;
|
|
|
ClearTransactionDate();
|
|
|
_isUpdating.Reset();
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public bool StartTransction()
|
|
|
{
|
|
|
if (_isUpdating.Set())
|
|
|
{
|
|
|
_removingDate.AddRange(_collection.Keys.ToArray());
|
|
|
return true;
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
#region IDisposable
|
|
|
public void Dispose()
|
|
|
{
|
|
|
Dispose(true);
|
|
|
GC.SuppressFinalize(this);
|
|
|
}
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
{
|
|
|
if (disposing)
|
|
|
{
|
|
|
if (_collection != null)
|
|
|
{
|
|
|
_collection.Clear();
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
#endregion
|
|
|
}
|
|
|
}
|