Хранилище для DataMappers в ASP.NET WebApplication - PullRequest
1 голос
/ 10 января 2012

В книге Мартина Фаулера «Шаблоны архитектуры корпоративных приложений» описан подход к организации DAL, подобный набору сопоставителей для сущностей.У каждого есть свой собственный IdentityMap, хранящий конкретную сущность.

, например, в моем веб-приложении ASP.NET:

//AbstractMapper - superclass for all mappers in DAL
public abstract class AbstractMapper
{
    private readonly string _connectionString;
    protected string ConnectionString
    {
      get { return _connectionString; }  
    }

    private readonly DbProviderFactory _dbFactory;
    protected DbProviderFactory DBFactory
    {
        get { return _dbFactory; }
    } 

    #region LoadedObjects (IdentityMap)
    protected Hashtable LoadedObjects = new Hashtable();

    public void RegisterObject(long id, DomainObject obj)
    {
        LoadedObjects[id] = obj;            
    }

    public void UnregisterObject(long id)
    {
        LoadedObjects.Remove(id);
    }
    #endregion

    public AbstractMapper(string connectionString, DbProviderFactory dbFactory)
    {
        _connectionString = connectionString;
        _dbFactory = dbFactory;
    }

    protected virtual string DBTable
    {
        get
        {
            throw new NotImplementedException("database table is not defined in class " + this.GetType()); 
        }
    }

    protected virtual T Find<T>(long id, IDbTransaction tr = null) where T : DomainObject
    {
        if (id == 0)
            return null;
        T result = (T)LoadedObjects[id];
        if (result != null)
            return result;
        IDbConnection cn = GetConnection(tr);
        IDbCommand cmd = CreateCommand(GetFindStatement(id), cn, tr);
        IDataReader rs = null;
        try
        {
            OpenConnection(cn, tr);
            rs = cmd.ExecuteReader(CommandBehavior.SingleRow);
            result = (rs.Read()) ? Load<T>(rs) : null;                  
        }
        catch (DbException ex)
        {
            throw new DALException("Error while loading an object by id in class " + this.GetType(), ex);
        }
        finally
        {
            CleanUpDBResources(cmd, cn, tr, rs);
        }
        return result;
    }

    protected virtual T Load<T>(IDataReader rs) where T : DomainObject
    {
        long id = GetReaderLong(rs["ID"]);
        T result = (T)LoadedObjects[id];
        if (result != null) 
            return result;

        result = (T)DoLoad(id, rs);
        RegisterObject(id, result);
        return result;
    }

    // another CRUD here ...
}

// Specific Mapper for entity Account
public class AccountMapper : AbstractMapper
{
internal override string DBTable
{
    get { return "Account"; }
}

public AccountMapper(string connectionString, DbProviderFactory dbFactory) : base(connectionString, dbFactory) { }

public Account Find(long id)
{
    return Find<Account>(id);
}

public override DomainObject DoLoad(long id, IDataReader rs)
{
    Account account = new Account(id);
    account.Name = GetReaderString(rs["Name"]);
    account.Value = GetReaderDecimal(rs["Value"]);
    account.CurrencyID = GetReaderLong(rs["CurrencyID"]);
    return account;
}

// ...

}

Вопрос: где хранить эти сопоставители?Как системные службы (сущности) должны вызывать мапперы?

Я решил создать MapperRegistry, содержащее все мапперы.Так что сервисы могут вызывать мапперы как:

    public class AccountService : DomainService
{
    public static Account FindAccount(long accountID)
    {
        if (accountID > 0)
            return MapperRegistry.AccountMapper.Find(accountID);
        return null;
    }
    ...
}

Но где я могу хранить экземпляр MapperRegistry?Я вижу следующие варианты, но не люблю ни один из них:

  1. MapperRegistry глобален для приложения (Singleton)

    • Не применимо из-за необходимости синхронизациив многопоточном приложении ASP.NET (по крайней мере Мартин говорит, что только безумный может выбрать этот вариант)
  2. MapperRegistry за сеанс

    • Кажется, неттак хорошо тоже.Все мастера ORM (NHibernate, LINQ to SQL, EntityFramework) советуют использовать DataContext (NHibernateSession, ObjectContext) для каждого запроса и не сохранять контекст в сеансе.
    • Кроме того, в моем WebApp почти все запросы являются AJAX-запросами к EntityController..asmx (с атрибутом ScriptService), возвращающий JSON.И сеанс не разрешен.
  3. MapperRegistry на запрос

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

Уважаемые эксперты, пожалуйста, помогите мне с архитектурным решением.

...