Сохранение шаблона состояния с помощью Entity Framework

В настоящее время я разрабатываю проект в MVC 3. Я разделил свои проблемы, поэтому есть такие проекты, как ядро, репозиторий, пользовательский интерфейс, службы и т. д. Я реализовал репозиторий, UnitOfWork и, самое главное, шаблон состояния.

Я использую Entity Framework 4.3 для сохранения своих данных, и я столкнулся с довольно раздражающей ситуацией, связанной с сохранением текущего состояния. Ниже приведены некоторые примеры классов:

public class Request
{
    public int RequestId { get; set; }

    public State CurrentState { get; set; }
}

public abstract class State
{
    [Key]
    public string Name {get; set;}

    public virtual void OpenRequest(Request request)
    {}

    public virtual void CloseRequest(Request request)
    {}
}

public class RequestIsOpenState : State
{
    public RequestIsOpenState()
    {
        this.Name = "Open";
    }

    public override void CloseRequest(Request request)
    {
        request.CurrentState = new RequstIsClosedState();
    }
}

public class RequestIsClosedState : State
{
    public RequestIsClosedState()
    {
        this.Name = "Closed";
    }

    public override void OpenRequest(Request request)
    {
        request.CurrentState = new RequstIsOpenState();
    }
}

Используя приведенный выше пример, я получу исключение нарушения первичного ключа, потому что оно попытается создать НОВОЕ состояние в таблице состояний.

Поскольку изменение состояния выполняется на уровне домена, я не могу просто «получить» состояние из репозитория и установить его с помощью внешнего ключа, выполнив что-то вроде этого:

Request request = unitOfWork.RequestRepository.Find(1);
request.CurrentState = unitOfWork.StateRepository.Find("Closed");

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

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


person Heberda    schedule 14.02.2014    source источник
comment
Если вы читаете полный пост, я знаю, что это возможно, этот вопрос нацелен на золотую середину.   -  person Heberda    schedule 19.02.2014
comment
Вам нужна таблица состояний, которая является глобальной (т. е. несколько записей, по одной для каждого возможного состояния — Открыто, Закрыто и т. д.) или локальной для Request (т. е. большая таблица с несколькими записями для каждого Request — по одной для каждого состояния Request прошел)?   -  person flipchart    schedule 24.02.2014
comment
Очевидно, было бы неплохо связать его с моделью предметной области для реляционных целей. На данный момент просто сохраняется изолированная таблица всех состояний.   -  person Heberda    schedule 24.02.2014
comment
Извините, я не думаю, что мой вопрос был ясен. Вам нужна таблица поиска состояний (которая должна быть предварительно заполнена), на которую будет ссылаться таблица Request, или вам нужна таблица RequestState, содержащая идентификатор Request и значение состояния?   -  person flipchart    schedule 24.02.2014
comment
О, я вижу, ну, в таблице состояний мне действительно не нужны никакие свойства реляционной навигации. Сохранение состояния позволило бы мне увидеть полный список состояний и связать другие свойства с тем же состоянием в запросе. Наличие его в качестве свойства ограничивает реляционную способность. Кроме того, если у меня нет запроса в состоянии Open, база данных не знает о его существовании.   -  person Heberda    schedule 24.02.2014


Ответы (2)


Я думаю, вы можете улучшить его, кэшируя экземпляры State, создавая его только один раз, чтобы не создавать список каждый раз и избегать foreach:

public static class StateFactory
{
    private static Dictionary<string, State> statesCache = FindAllDerivedStates();

    public static State GetState(string stateTypeName)
    {
        return statesCache[stateTypeName];
    }

    private static Dictionary<string, State> FindAllDerivedStates()
    {
        var derivedType = typeof(State);
        var assembly = Assembly.GetAssembly(typeof(State));
        return assembly.GetTypes().Where(t => t != derivedType && derivedType.IsAssignableFrom(t))
                    .Select(t => (State)Activator.CreateInstance(t))
                    .ToDictionary(k => k.Name);
    }
}
person thepirat000    schedule 23.02.2014
comment
Мне это очень нравится! Ваше здоровье. - person Heberda; 23.02.2014
comment
@Heberda Heberda, вы можете пометить вопрос как ответ, если это решит вашу проблему. - person thepirat000; 23.02.2014
comment
Да, я знаю, спасибо, это просто не ответило на первоначальный вопрос, который был проще, чтобы сохранить состояние. - person Heberda; 23.02.2014

Я добился некоторого прогресса, упростив фабрику до основ и реализовав ее таким образом, что вы никогда не узнаете, что фабрика используется. Хотя это не то, что я искал, оно настолько усовершенствовано и упрощено, что единственным недостатком является то, что у меня до сих пор нет списка ВСЕХ состояний в базе данных SQL, однако есть много возможных обходных путей для этого. В любом случае... мой компромисс:

Государственный завод:

public static State GetState(string stateTypeName)
{
    var list = FindAllDerivedStates();
    dynamic returnedValue = new NullState();
    foreach(var state in list)
    {
        if(state.Name == stateTypeName) returnedValue = (State)Activator.CreateInstance(state);
    }
    return returnedValue
}

private static List<Type> FindAllDerivedStates()
{
    var derivedType = typeof(State);
    var assembly = Assembly.GetAssembly(typeof(State));
    return assembly.GetTypes().Where(t => t != derivedType && derivedType.IsAssignableFrom(t)).ToList();
}

Теперь запросу нужны два свойства: сохраняемая строка и класс State. Убедитесь, что класс State не сопоставлен.

public class Request
{
    public string StateString { get; set; }

    [NotMapped] or [Ignore]
    public State CurrentState 
    { 
        get
        {
            return StateFactory.GetState(this.StateString); 
        }
        set
        { 
            this.State = value.GetType().Name; 
        }
    }
}

Теперь из-за новой упрощенной реализации сохранить состояние так же просто, как;

request.CurrentState = new OpenState();

и получение состояния всегда будет возвращать методы. Без какой-либо дополнительной работы вы можете вернуть сущность и превысить свойства. Например, если вы хотите вывести общедоступную строку;

request.CurrentState.StateName;

Теперь мне еще нужно немного поработать, чтобы добавить список состояний в мой SqlDb, но это еще не конец света. Кажется, это единственное решение. Или я должен сказать лучшее решение. Я буду держать глаза очищены для лучшей версии.

person Heberda    schedule 19.02.2014
comment
Вероятно, вам следует избегать выполнения GetTypes() каждый раз, когда вы вызываете GetStates. Лучше хранить List<Type> в статическом поле и загружать его один раз в конструкторе. - person thepirat000; 23.02.2014
comment
Как в переменной внутри состояния? Это звучит довольно разумно. Существуют ли большие накладные расходы при использовании GetType()? - person Heberda; 23.02.2014