Нужна помощь, чтобы разобраться с головной болью абстрактного шаблона в моем DAL - PullRequest
3 голосов
/ 02 ноября 2009

У меня возникла небольшая проблема с моим уровнем доступа к данным. В данном конкретном случае у меня есть таблица, которая содержит потенциально 5 типов «сущностей». В основном это Company, Customer, Site и т. Д. Тип определяется значением PositionTypeId в таблице. Все они находятся в одной таблице, поскольку имеют одинаковую структуру данных; PositionId, описание и код .

У меня есть основной абстрактный класс следующим образом:

public abstract class PositionProvider<T> : DalProvider<T>, IDalProvider where T : IPositionEntity
{
    public static PositionProvider<T> Instance
    {
        get
        {
            if (_instance == null)
            {
                // Create an instance based on the current database type
            }
            return _instance;
        }
    }
    private static PositionProvider<T> _instance;

    public PositionType PositionType
    {
        get
        {
            return _positionType;
        }
    }
    private PositionType _positionType;

    // Gets a list of entities based on the PositionType enum's value.
    public abstract List<T> GetList();

    internal void SetPositionType(RP_PositionType positionType)
    {
        _positionType = positionType;
    }

}

Я хочу иметь возможность поместить весь общий код в класс наследования, основанный на SQL или Oracle. Это моя реализация SQL:

public class SqlPositionProvider<T> : PositionProvider<T> where T : IPositionEntity
{
        public override List<T> GetList()
        {
            int positionTypeId = (int)this.PositionType;
            using (SqlConnection cn = new SqlConnection(Globals.Instance.ConnectionString))
            {
                SqlCommand cmd = new SqlCommand("Get_PositionListByPositionTypeId", cn);
                cmd.Parameters.Add("@PositionTypeId", SqlDbType.Int).Value = positionTypeId;
                cmd.CommandType = CommandType.StoredProcedure;
                cn.Open();
                return this.GetCollectionFromReader(this.ExecuteReader(cmd));
            }
        }
}

Затем я создал класс для каждого типа следующим образом (это, например, CustomerProvider):

public class CustomerProvider
{
    public static PositionProvider<CustomerEntity> Instance
    {
        get
        {
            if ((int)PositionProvider<CustomerEntity>.Instance.PositionType == 0)
            {
                PositionProvider<CustomerEntity>.Instance.SetPositionType(PositionType.Customer);
            }
            return PositionProvider<CustomerEntity>.Instance;
        }
    }
}

Это все работает фантастически ... пока я не понял, что у меня есть определенные функции, которые конкретно связаны с определенными типами позиций. То есть Мне нужно иметь возможность получать всех клиентов (это IPositionType) на основе разрешений пользователей.

Так что мне нужно добавить еще один абстрактный метод:

public abstract List<CustomerEntity> GetCustomersByUserPermission(Guid userId);

Теперь, очевидно, я не хочу, чтобы это было в моем абстрактном классе PositionProvider, поскольку это означало бы, что этот метод появится при работе с поставщиком сайта / компании.

Как добавить этот и другие дополнительные методы, не дублируя код в SqlPositionProvider?

Изменить:

Единственная идея, с которой я столкнулся, - это разделить PositionProvider на общее свойство CustomerProvider, SiteProvider и т.д.Provider:

public abstract class CustomerProvider
{

    public CustomerProvider()
    {
        this.Common.SetPositionType(PositionType.Customer);
    }

    public PositionProvider<CustomerEntity> Common
    {
        get
        {
            if (_common == null)
            {
                DalHelper.CreateInstance<PositionProvider<CustomerEntity>>(out _common);
            }
            return _common;
        }
    }
    private PositionProvider<CustomerEntity> _common;

    public static CustomerProvider Instance
    {
        get
        {
            if (_instance == null)
            {
                DalHelper.CreateInstance<CustomerProvider>(out _instance);
            }
            return _instance;
        }
    }
    private static CustomerProvider _instance;

    public abstract List<CustomerEntity> GetCustomersByUserPermission(Guid userId);

}

Это позволило бы мне поместить конкретный код в CustomerProvider.Instance.MyNonGenericMethod(), а затем получить доступ к PositionProvider. Я мог бы сделать CustomerProvider.Instance.Common.GetList() ... Хотя это выглядит как взлом.

Ответы [ 5 ]

1 голос
/ 02 ноября 2009

Если я правильно понимаю, вам нужен способ включить какой-либо метод в дочерние классы, но не во все.

Если вы можете сгруппировать дополнительные методы, которые вам нужны, вы можете использовать интерфейс, реализовать его и использовать экземпляр этого нового класса внутри ваших детей (состав).

Упрощение этого - шаблон хранилища для всех ваших дочерних классов (в этом примере не используются интерфейсы).

[ПРИМЕЧАНИЕ: код не может быть скомпилирован, только для демонстрационных предложений]

public class PositionProviderRepository
{
    public List<T> GetList()
        {
            int positionTypeId = (int)this.PositionType;
            using (SqlConnection cn = new SqlConnection(Globals.Instance.ConnectionString))
            {
                SqlCommand cmd = new SqlCommand("Get_PositionListByPositionTypeId", cn);
                cmd.Parameters.Add("@PositionTypeId", SqlDbType.Int).Value = positionTypeId;
                cmd.CommandType = CommandType.StoredProcedure;
                cn.Open();
                return this.GetCollectionFromReader(this.ExecuteReader(cmd));
            }
        }
    public List<CustomerEntity> GetCustomersByUserPermission(Guid userId) {
      //TODO: implementation
    }
}

И затем вы используете этот класс внутри всех сущностей, таких как CustomerEntity.

Это может эффективно заменить ваш класс SqlPositionProvider<T>, но я не уверен, что правильно понимаю вашу архитектуру, у вас очень сложная иерархия.

1 голос
/ 02 ноября 2009

«Правильным» местом для такого метода поиска будет класс Repository .Там вы можете собрать все такие функции запросов вдали от своих доменных объектов.

Вот небольшой пример:

public static class Repository {
    public static List<CustomerEntity> GetCustomersByUserPermission(
        PositionProvider<CustomerEntity> source, Guid userId)
    {
        // query source and return results
    }
}

Добавьте все ваши «специальные» запросы к этому классу.

0 голосов
/ 02 ноября 2009

Я взломал это. Мой класс Наследования теперь стал следующим:

public abstract class CustomerProvider : PositionProvider<CustomerEntity>
{

        public CustomerProvider() { }

        public new static CustomerProvider Instance
        {
            get
            {
                if (_instance == null)
                {
                    DalHelper.CreateInstance<CustomerProvider>(out _instance);
                }
                return _instance;
            }
        }
        private static CustomerProvider _instance;

        public override List<CustomerEntity> GetList()
        {
            return PositionProvider<CustomerEntity>.Instance.GetList();
        }

        public abstract List<CustomerEntity> GetCustomersByUserPermission(Guid userId);

}

Имеет конкретную реализацию следующим образом:

public class SqlCustomerProvider : CustomerProvider
{
    public override List<CustomerEntity> GetCustomersByUserPermission(Guid userId)
    {
        using (SqlConnection cn = new SqlConnection(Globals.Instance.ConnectionString))
        {
            SqlCommand cmd = new SqlCommand("GetRP_CustomersByUser", cn);
            cmd.Parameters.Add("@UserId", SqlDbType.UniqueIdentifier).Value = userId;
            cmd.CommandType = CommandType.StoredProcedure;
            cn.Open();
            return this.GetCollectionFromReader(this.ExecuteReader(cmd));
        }
    }
}

Мой PositionProvider остается прежним, но, вызывая его в переопределениях в расширенном CustomerProvider, он затем использует SqlPositionProvider для кода конкретного поставщика.

Теперь я могу достичь того, чего хотел.

// Returns a list of customers still using the PositionProvider
CustomerProvider.Instance.GetList(); 

// Returns my specific customer data
CustomerProvider.Instance.GetCustomersByUserPermission();

// Returns a list of sites still using the PositionProvider
SiteProvider.Instance.GetList(); 

// Not part of the SiteProvider!
SiteProvider.Instance.GetCustomersByUserPermission(); 
0 голосов
/ 02 ноября 2009

Во-первых, .NET BCL имеет хороший уровень абстракции для различных СУБД, определенных в System.Data.Common. Используя DbConnection вместо SqlConnection / OracleConnection, DbCommand вместо SqlCommand / OracleCommand и т. Д., Вы сможете немного уменьшить дублирование кода (могут возникнуть ошибки, такие как различия в параметрах) называть, но их можно преодолеть).

Во-вторых, IMHo, это плохая идея строить весь свой код вокруг синглетонов. Почему бы тебе не написать

public class CustomerProvider
{
    PositionProvider<CustomerEntity> _provider;
    PositionProvider<CustomerEntity> Instance // we don't need it public really.
    {
        get
        {
            if ((int)PositionProvider<CustomerEntity>.Instance.PositionType == 0)
            {
                _provider = new PositionProvider<CustomerEntity>(); // PositionType is set in .ctor
                // we can also use a factory to abstract away DB differences
            }
            return _provider;
        }
    }
    // one way of implementing custom query
    public List<CustomerEntity> GetCustomersByUserPermission(Guid userId){
        return _provider.GetListWithCriteria(Criteria.Argument("UserId", userId));
    }
}

Метод GetListWithCriteria может быть реализован как:

public List<CustomerEntity> GetListWithCriteria(params ICriterion[] criterias){
        int positionTypeId = (int)this.PositionType;
        using (DbConnection cn = OpenConnection()) // creates DbConnection and opens it
        using (DbCommand cmd = cn.CreateCommand())
        {
            // ... setting command text ...
            foreach(ICriterion c in criterias){
                DbParameter p = cmd.CreateParameter();
                p.DbType = c.DbType;
                p.Name = Encode(c.Name); // add '@' for MS SQL, ':' for Oracle
                p.Value = c.Value;
                cmd.AddParameter(p);
            }
            return this.GetCollectionFromReader(this.ExecuteReader(cmd));
        }        
}

Таким образом, PositionProvider остается способом абстрагирования от различий СУБД, и CustomerProviders может создавать произвольные новые запросы.

0 голосов
/ 02 ноября 2009

Как насчет добавления чего-то подобного в ваш абстрактный класс:

public IEnumerable<T> GetItems(Predicate<T> match)
{
    foreach (T item in GetList())
    {
        if (match(item))
           yield return item;  
    }
}

А затем вам следует удалить метод SetPositionType(...), потому что его использование кажется немного неудобным (вы должны установить тип позиции и затем вызвать GetList()?)

Итак, используя метод, вы можете просто написать:

customerProvider.GetItems(customer => customer.Id == someId);

(или с использованием синтаксиса .Net 2.0)

customerProvider.GetItems(delegate(Customer c)
{
     return c.Id == someId;
});
...