Pregunta

I am currently developing a project in MVC 3. I've separated my concerns so there are projects such as Core, Repository, UI, Services etc. I have implement the Repository, UnitOfWork and most importantly the State pattern.

I am using Entity Framework 4.3 to persist my data and I have come across a rather annoying situation involving the persistence of the current state. Below are some class examples:

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();
    }
}

Using the above example I will get a primary key violation exception because it tries to create a NEW state in the States table.

Because the state change is done within the domain layer, I can't just 'get' the state from the repository and set it using the foreign key by doing something like this:

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

I'm aware I have the option of not mapping the state property, and persist a string property in the request class and then convert them back and forth through a factory on a get and set when the entity is hydrated (see this answer).

All I want to do is persist the state class, so when the request is returned I can access the state methods immediately without having loads of EF stuff polluting my domain layer just to handle one persistence issue. Another benefit of which would be it gives me the added bonus of having a table in SQL to query against known states.

¿Fue útil?

Solución 2

I've made some progress by simplifying the factory back to basics and by implementing it in such a way that you would never really know that a factory is being used. Although It's not what I was looking for, it is so refined and streamlined the only downside is I still don't have a list of ALL states within the SQL database, there are however many possible work arounds for this. Anyway... my compromise:

The State Factory:

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();
}

Now the request needs two properties, a persisted string and a State class. Make sure the State class is not mapped.

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; 
        }
    }
}

Now because of the new simplistic implementation, saving the state is as easy as;

request.CurrentState = new OpenState();

and getting the state will always return the methods. Without any extra work you can return an entity and excess the properties. For example if you want output the public string;

request.CurrentState.StateName;

Now I've still got to implement a little work around to add a list of states to my SqlDb but that's not the end of the world. It seems this is the only solution. Or should I say best solution. I'll keep my eyes peeled for a better version.

Otros consejos

I think you can improve it by caching the State instances creating it only once, to avoid making the list each time and avoid the 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);
    }
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top