views:

540

answers:

10

In an application I'm building I had an enumeration of account statuses:

 public enum AccountStatus
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

However, I needed more information from an AccountStatus so I made a class which has a few extra useful properties:

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
 }

This class get populated from a database table that might look like this:

 1,  "Active",       True,  True
 2,  "Trial",        True,  True 
 3,  "ExpiredTrial", False, True
 4,  "Expelled",     False, False

This is really handy when I have a customer object that uses the AccountStatus because I can write code like:

 if(customer.Status.CanReactivate) // Show reactivation form

However, I have lost something equally important. I can no longer do this:

 if(customer.Status == AccountStatus.Active)  // allow some stuff to happen

What would be the best way, if its even possible, to include something that will allow me to mimic the enumeration within the class. I know that I could add public static fields to the AccountStatus class, but ultimately this doesn't work because if the database changes the code would have to be manually updated. By this, I mean:

 public static readonly AccountStatus Active = new AccountStatus(1);
 public static readonly AccountStatus Trial = new AccountStatus(2);
 // etc, etc ...

I imagine there is probably a pattern for this somewhere, I just don't know what its called.

Any ideas?

CLARIFICATION

Based on the answers so far I need to clarify a couple of things.

The table above is a brief example. In my actual table there a many records, I have 12 in there right now. Plus we can add more or remove some existing. This is what I meant by "dynamic" in my question title.

Secondly, I gave a very simple use case for the ability I lost which apparently confused matters. Here is another real example:

 if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

... neither Trial nor ExpiredTrial are boolean values on the property. I don't want to add them either. That would set an even worse precedent than the one I'm trying to avoid (meaning I would have to add a new property to the class every time I added a new record to the table).

UPDATE

I selected an answer which didn't really meet was I was looking for, but suggests that I was looking for something unnecessary. After thinking about this, I concur. While adding an enum or static fields does duplicate some work (ie, having the values in both code and in a table) I think the benefits outweigh the negatives.

A: 

You could make a many-to-many relationship between AccountStatus and Description. This way you can, at runtime, load all the different Descriptions you got, and then compare against those, using some sort of enumeration :)

cwap
+1  A: 

I think you could achieve this by using a Flags enumeration where you can combine values:

[Flags]
public enum AccountStatus
{
    Expelled = 1,
    Active = 2,
    CanReactivate = 4,
    Canceled = 8,
    Trial = Active | CanReactivate,
    ExpiredTrial = CanReactivate,        
}

However, it feels as if those different enum values move along different scales (some describe state, some describe valid actions), so it might not be the right solution. Perhaps you should instead split it into two enums.

Fredrik Mörk
Interesting, I hadn't thought of using flags which might solve the situation in some cases. However, this could get messy if I decided to add another status type, or just as bad, another "property".
Sailing Judo
+4  A: 

But why can't you use the enumeration as a property of that class..?

public enum State
{
    Active = 1,
    Trial = 2,
    Canceled = 3
}

public class AccountStatus
{
    public int Id {get; set;}
    public State State {get; set;}
    public string Description {get; set;}
    public bool IsActive {get; set;}
    public bool CanReactivate {get; set;}
}

And then:

if(customer.Status == AccountStatus.State.Active)  // allow some stuff to happen
Pawel Krakowiak
This is about the same net result as using the readonly fields. Where this becomes a problem is some months down the line another developer may see the table with the AccountStatus values and say to himself, "Ah! All I have to do is add a new record here and my job is done!" Not to mention is would sort of violate the DRY principle.
Sailing Judo
@Sailing I don't understand. If a dev adds a new AccountStatus row, of course he needs to add code to work with it. There is no way around that. On the other hand, none of these solutions will break if a row is added - they just won't recognize it until it is added to the code.
Gabe Moothart
You might be right. The only use case I can think of that would *not* require code from a developer is if the AccountStatus values were presented as a list to a user... say for sorting or filtering purpose. Most other use cases would require the dev to do something, somewhere in the code. I think this makes this answer (or the static version) acceptable, but still not necessarily ideal. I'll leave the question open still in hopes that somebody else has a good idea too, but otherwise this might be the right answer.
Sailing Judo
It can only by fully dynamic when the system behavior depending on the status is specified outside of the code, eg. in a scripting language or something. All other solutions would require some code to be written when the kinds of status change, or when business rules change.
Emile Vrijdags
There will always be some developer who will write code, otherwise there is no way to use the new status in conditional clauses. If you decide that developer-friendliness is your concern you could implement a class that behaves like a enumeration and could encapsulate behavior. I blogged about this some days ago in case you are interested: http://www.tigraine.at/2009/08/02/a-better-way-to-write-enumerations/
Tigraine
A: 

This code does what you described in your post. I didn't code CanReactivate because you didn't say what the logic for that was.

 public enum AccountStatusCode
 {
      Active = 1,
      Trial = 2,
      Canceled = 3
 }

 public class AccountStatus
 {
      private AccountStatusEnum status
      //
      // Constructor sets initial status.
      public AccountStatus(int status)
      {
          this.Status = (AccountStatusCode)status;
      }

      public int Id { get; set; }
      public string Description { get; set; }
      //
      // 
      public bool IsActive 
      { 
           get { return status == AccountStatusCode.Active; }
      }
      public bool CanReactivate { get; set; }
 }

Note that, since you said you wanted to specify the initial account status as an int, I accept an int in the constructor, but then I cast it to an AccountStatusEnum for assigning it to the member variable. That's probably not the best practice...You should pass the constructor an AccountStatusCode value.

Robert Harvey
+1  A: 

I don't understand why you can't just write:

if (customer.Status.IsActive)
Pavel Minaev
Well, that example might have been simplistic. How about "if (customer.Status == AccountStatus.ExpiredTrial)" I don't have a property for that in AccountStatus and I don't want to add a property for every case that might come up. The code above was just an example... my table has 14 records in it representing different states a customer account can be in.
Sailing Judo
Pavel Minaev
I can think of several use cases where this wouldn't work. For example, if I want to get a list of customers whose status equaled a user selected AccountStatus. Plus there are several circumstances where duplicate booleans are on several AccountStatus... but the the fact that they are different statuses place them in different workflows.
Sailing Judo
+2  A: 

Rather than working with a strongly-typed enum, you could just do comparisons using a string:

public static readonly AccountStatus Active = new AccountStatus("Active");

or load the type from your database:

public static readonly AccountStatus Trial = new AccountStatus( reader["StatusField"] );

You can then do explicit comparisons:

if(customer.Status == "Active")

You lose the strong typing, but that's what dynamic means :-). You can store the known string values in constants to get some of this back.

edit

You could of course do this using the corresponding integer values, like you hinted at the end of your post. But strings are easier to read, and in this case using integers doesn't offer any sort of typing benefits.

Gabe Moothart
I didn't -1 this... I really wish ppl would comment when they mark an answer down. However, you're right. I used an int in the contructor of my sample code. Its horrible that I did that but it was a side effect of not having enums anymore. Strings are more readable but just as bad. I'm going to +1 this just to offset the unwarranted (or at least unexplained) negative.
Sailing Judo
A: 

OK, well if you're working in C# 3.0, you could try extension methods:

// Define extension method like this:
public static bool IsActive(this AccountStatus status)      
{            
    get { return status == AccountStatusCode.Active; }      
}
// 
// Call it like this:
if (AccountStatus.IsActive())

That keeps it out of your class.

Robert Harvey
i think this misses the point although it does start making me think of alternative directions. (by missing the point, i mean that your suggestion has the same effect has making an enum or static field, but takes more code to do so. I'd have to add an extension for every record in the table rather than just an enum value).
Sailing Judo
+1  A: 

if you do/want something like this in your application:

if(customer.Status == AccountStatus.Active)

You have to know in your code that "Active" is a possible status. How else would you be able to write the actual word Active in your code. The status object can be dynamic, but the rest of the program that uses the status has to know what types of status exist in order to do something useful with it. What if active doesn't exist anymore, the status object may not need to be reimplemented, but the code that uses it does.

If every kind status is fully defined by parameters like it almost seems (active and trail have the same parameters, so more are needed to differentiate (expiration date?)), then check those parameters.

If a combination of parameters has to have a name, then make some kind of lookuptable where you can translate the name into its associated parameters, or its inverse. That way the name can be dynamic for that piece of code, and the parameter values are to upto some degree.

A possible real dynamic solution would be to implement some sort of scripting language/xml file/... that enables the user to specify the kinds of status, their parameters, and associate them with system behavior.

Emile Vrijdags
+1  A: 

I still think your best bet is to add your missing cases to the class.

 public class AccountStatus
 {
      public int Id {get; set;}
      public string Description {get; set;}
      public bool IsActive {get; set;}
      public bool CanReactivate {get; set;}
      public bool Trial {get; set;}
      public bool ExpiredTrial {get; set;}
 }

Which you can call in a simpler form than your example:

if(customer.AccountStatus.Trial || customer.AccountStatus.ExpiredTrial)

If you need to check a UserDefined status, expose that as a separate property:

public AccountStatusCode Status  {get; set;}

...and call it like this:

if(customer.Status == AccountStatus.Active)

You can still add a constructor to it if you want to set an initial status.

Robert Harvey
Sorry, didn't realize Pavel already solved it.
Robert Harvey
Still a useful response though... thanks.
Sailing Judo
A: 

I think what judo tried to explain is - a new status in the DB will require to put checking s for that new status in the conditional blocks. I think I am also doing something same. Only thing I am doing is I am also using another 'rank' field so that I can do range comparison instead of hard coding all the statuses. For example instead of doing:

if(customer.Status == AccountStatus.Trial || customer.Status == AccountStatus.ExpiredTrial)

if I could put them in order I could do:

if(customer.Status < AccountStatus.Trial) as in our enum we can put them as ordered. So a new status in a new page wont break the other page's logics (depends on the rank of the status).

Rahat