My application has a model which currently uses an integer in the SQL database to store the value of a [Flags]Enum. The Enum looks something like this:
namespace MyProject.Models
{
[Flags]
public enum MyEnum : int
{
FirstThing = 1,
SomethingElse = 2,
YetAnotherOne = 4
}
}
So if a particular row had this field set to 3, it means flags FirstThing and SomethingElse are both set. Right now I'm using a helper class to convert and check MyEnum values to/from/against the integer, which does work, but I think there's gotta be a way to map the SQL INT field directly to the enum.
Basically the end goal is to have a list of checkboxes, one for each possible flag that will eventually be saved in the database as an INT.
Is this a good idea? If so, how do I go about this? If not, should I just suck it up and write out all that code myself (instead of using some nifty tricks)?
You will need that helper class, neither OR mapper fully supports mapping int to enum. There are ways around it, but that's more of a replication of the target behaviour with gapping holes in it than anything near the wanted effect.
Related
So far I have soemthing like this:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2
}
And in the view, if I use:
Html.EnumDropDownListFor(x=>x.EnumProperty)
While having:
public MyEnum EnumProperty {get;set;}
On the view model, the dropdown works as expected(I see FirstElement, and the value attached is element1)
The problem is that I want to have a dash in the value, like this:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2,
[Display(Name="Third")]
element-dashed = 3
}
It won't work, as element-dashed is not a valid entry.
This won't work either:
[Value("element-dashed")]
[Display(Name="Third")]
elementDashed = 3
The answer to your question is simple. But it warrants an explanation, to explain why this is the answer.
There is no functional reason to want a dash in an enum.
Let's say I have a working application, which contains your enum:
public enum MyEnum
{
[Display(Name="FirstElement")]
element1 = 1,
[Display(Name="SecondElement")]
element2 = 2
}
Suppose I change the enumerator names:
public enum MyEnum
{
[Display(Name="FirstElement")]
BatmanIsBruceWayne = 1,
[Display(Name="SecondElement")]
SupermanIsClarkKent = 2
}
I am assuming that every usage of the enum in code is also adjusted accordingly. If you use the Rename method in VS (F2), this is automatically the case.
Nothing has changed about how the application works. Functionally speaking, the name of an enum does not matter.
public enum CardSuit { Hearts, Spades, Diamonds, Clubs }
public enum CardSuit { TheRedOne, TheBlackOne, TheOtherRedOne, TheOtherBlackOne }
Of course, the first one is much more readable for developers than the second one. However, a compiler does not care about the meaning of the enum names. It only cares that the appropriate value is used consistently.
This is the same principle as variable names. While we should of course take care to make variable names readable by developers; there is no technical reason (i.e. to the functionality of the application) why we should use int myDescriptiveInteger over int x.
We only choose a descriptive name for the purposes of readability for humans who maintain the code.
There is a single exception to this: If you are calling .ToString() on an enum, in order to have a string representation of the field you are using.
Note: While this is technically possible and has been done before by others; I do wonder if that's really what you should be doing here. You're already assigning explicit int values to the enum values. Those int values are much easier for saving/loading an enum instead of trying to work with its string equivalent. While both are technically possible, using the int is far better due to a smaller footprint and a lack of the naming problem that you are wrongly trying to fix here.
It is not clear from your question whether this is the case for you. Maybe it's done somewhere in your application, maybe it's not. I can't know that without you telling me.
If it is not done, then your problem is functionally irrelevant. The name does not matter except for readability purposes, and I'm quite convinced that your developers won't suddenly stop understanding the intent of the code because there's a dash missing.
If it is done, then you actually have already provided the answer to your problem. Instead of calling .ToString() on the enum; instead you must use the value of [Display(Name="FirstElement")] as the correct "string name" for your enum.
public static string GetName(this myEnum enumValue)
{
return enumValue.GetType()
.GetMember(enumValue.ToString())
.GetCustomAttribute<DisplayAttribute>()
.GetName();
}
And then you can change the following code:
myEnumValue.ToString();
To the following code, which correctly uses the attribute's value instead of the enum field name:
myEnumValue.GetName();
In both cases, the core answer remains the same: You do not need to worry about the specific name of your enum fields because it is functionally irrelevant.
Edit If you want your enum value to be displayed differently in the combobox than you want it to be when you save the value, then use a second attribute to differentiate between the two. But the principle still stands: do not base yourself off of the field names than an enum uses.
And I would like to stress again that you should be using the integer values of your enum. I see no reason why you would explicitly assign int values to your enum and then refuse to use them.
I am currently using Entity Framework for a project and one of my classes have an Enum representing some values.
So far EF is saving the Enums as numbers in the database, but I wanted to save them as their actual string names. For example, the Enum NY is saved as 1, instead of "NY".
I have already seen some ways to make this work, like having a string property with a hidden Enum private field, but I wanted to know if there is a way I can just Intercept EF when it's doing the CRUD operations and then I can change the Enum to a String in the Data Context class.
No, you cannot do that directly: when you map your class with an Enum property, that property is mapped to a database int column, and you cannot change that in any way. I mean that, as you cannot change the model, there is no way to intercept and convert the Enum property value into an string, because the model stubbornly wants an int.
That said, there are several ways to make it work:
having an string property for the key and a [NotMapped] Enum property that updates that key. SEE THE NOTE: But the key must be public, and thus accesible through the application code.
using a class that have only the enum property and is used in your application domain, and a different class which is used for your EF model, and map the values, for example using ValueInjecter or Automapper
I usually take the first path and use an attribute that allows me to define the string key for each Enum value, so you can reuse this pattern in all the cases in which need to do this.
NOTE: this part of the answer was wrong: you can map any property regardles of the modifier (public, protected, private, internal...). EF conventions only include the public properties, and there are no data annotations that can overcome this limitation. But you can use it with the Fluent API. However, as the property is private,you cannot access it directly using the Fluent API. There are several solutions to do it described here: Code First Data Annotations on non-public properties
If you follow this path, you can have a class like this:
public class MyEntity
{
// ...
[NotMapped]
public EnumType Value
{
get { /* return KeyForEnum converted to EnumType value */ }
set { /* set KeyForEnum value from the received EnumType value*/}
}
// Use some mechanism to map this private property
private string KeyForEnum { get; set; }
// ...
}
As you can see, if you use a class like this, in the app the entity will have a property of EnumType type, but in the database it will be an string.
One of the tricks to be able to map it through Fluent API is this:
1) Add an static property that returns an expression able to select the property from an object of this class, i.e.
public static readonly Expression<Func<MyEntity,string>> KeyForEnumExpression
= me => me.KeyForEnum;
2) Use it in the fluent API to get the property mapped, like so:
modelBuilder
.Entity()
.Property(MyEntity.KeyForEnumExpression)
LAST NOTE: This will modify the POCO class by adding the static readonly property. You can use Reflection instead to build an expression to access the private property, like you can see here: EF 4.1 Code First, ¿map private members?. It's in Spanish, but you can look directly at the code
I’d like to use an attribute for parsing strings to enum values (something like in this question), but I’d like to use multiple strings for each enum value, e.g.:
enum Foo
{
[SomeAttribute("A BAR")]
[SomeAttribute("The BAR")]
Bar,
[SomeAttribute("A BUZZ")]
[SomeAttribute("The BUZZ")]
Buzz
}
That means that I cannot use Description nor DisplayName because they have AllowMultiple=false.
I know that it is simple to create my own attribute, but:
Does the .NET framework already contain a suitable attribute that I can use for this?
No it doesn't. Go with creating your own attribute.
The title asks it all. In the database I have legacy data that contains titles of documents that have spaces such as "Title Holder" and so on. I want to be able to map these directly to an enum with Fluent NHibernate but I am encountering parsing errors. I have been unable to find any indication of a custom converter I can use, are there any recommendations someone can make?
My mapping looks like this.
Map(x => x.DocumentName).Nullable().CustomSqlType("varchar(50)");
You are going to need to implement your own NHibernate IUserType and the bulk of your logic will be in the NullSafeGet() and NullSafeSet() methods.
You'll also need to create your own internal enum to string mapping. You could use a dictionary that would hold the string as the key and the enum value as the value and so your logic would basically revolve around looking up values in that dictionary to convert from a string to an enum and vice versa. Another option could be to use an attribute to decorate each of your enum values with the string version of it's name and then at runtime do conversion with reflection...
Here are some examples of creating a custom IUserType: ( The first link below should really point you in the right direction )
Mapping Strings to Booleans Using NHibernate’s IUserType
Mapping different types - IUserType
Implementing custom types in nHibernate
It is possible to write a custom type that gets rid of the spaces when the data is read from database and then you can map the converted string to an enum. Problem with this approach would be when saving data back to database because you would not know where to add the space back in (unless you are happy with spaghetti code to keep track of where to insert the spaces back).
Alternatively, you can have an additional property on the class of type enum that returns the enum based on what is in the property mapped to database. Example below
public class Document
{
public virtual string DocumentName {get; set;}
public EDocumentName Name
{
get
{
if (DocumentName == "Title Holder")
{
return EDocumentName.TitleHolder;
}
}
set
{
if(value == EDocumentName.TitleHolder)
{
DocumentName = "Title Holder";
}
}
}
}
public enum EDocumentName
{
TitleHoldder
}
Does anyone know of a quick way to have NHibernate insert default values for value types as null into the database?
For example:
public class Foo
{
public int SomeProperty { get; set; }
}
By default, int is 0. When it goes into the database, I would like it to go in as null.
I know I could do this by changing SomeProperty to a nullable int, but perhaps there is another way?
I also know that I could do this with an IUserType but is there an easy way of building a user type that can be generically used for all value types?
This is not a simple way to make NHibe convert 0 to null and vice-versa, but an alternative suggestion (maybe or maybe not right in your situation).
Imo, using a default value to mean null is an antipattern; maybe you agree. I take it that you're trying to hook up some legacy code with a proper database; is that correct?
If you are in control of the business class which you are mapping (class Foo), then I recommend exposing a nullable version of SomeProperty for use moving forward:
public class Foo
{
/// for new code, and for mapping to the database:
public int? SomeProperty_new {get;set;}
/// for legacy code only. Eventually, refactor legacy code to use SomeProperty_new instead, and just remove this needless property.
[Obsolete("This uses default value to mean null. Use SomeProperty_new instead.")]
public int SomeProperty_old
{
get
{
if (SomeProperty_new == null)
return 0;
else
return SomeProperty_new;
}
set { /* convert from 0 to null if necessary and set to SomeProperty_new.*/ }
}
}
You'll want better names than SomeProperty_new and SomeProperty_old, though.
On the other hand, if 0 is never a valid value (except to mean null), you could instead make the DB use a non-nullable value. It really depends on the situation at hand.
You could use NHibernate event architecture and plug a Listener doing the conversion on load, save or update.