简体   繁体   中英

Seamless way to store and retrieve enums from a string field using EF Model

I have several enums, that represent states, and all correspond to string fields on the EF model. All enums values are chosen so they can be easily represented as chars, so they are set like this:

public enum eStateCode
{
    Running = 'R',
    Terminated = 'T',
    Cancelled = 'C',
}

This way database field can be a char[1] type, capable of storing meaning codes, instead a abstract numeric field. This approach works seamless using Linq to SQL, because char SQL columns are mapped to char fields, but not using EF.

Using EF, when i try to use it on a lambda expression like this

s => s.WFStateCode == ((char)ClassX.eStateCode.Running).ToString())

i get the

LINQ to Entities does not recognize the method

error, meaning that ((char)ClassX.eStateCode.Running).ToString() was sent to SQL.

I can circumvent the problem by putting put ((char)ClassX.eStateCode.Running).ToString() in a variable but makes the code much more verbose. I read several posts but none say how I can force EF to resolve the part of expression on the client side.

It is possible to define a method that can be used in a lambda expression as a literal, so it will not be sent to SQL?

The error means that EntityFramework cannot translate your code into SQL. You can convert it to string before sending to EntityFramework:

string stateCode = ((char)ClassX.eStateCode.Running).ToString();
.............
s => s.WFStateCode == stateCode

Update

You can change your model and set type of WFStateCode to eStateCode . By doing so, you will not need any conversion. EntityFramework supports enums from EF5. Your final code should be something like:

s => s.WFStateCode == ClassX.eStateCode.Running

It is possible to define a method that can be used in a lambda expression as a literal, so it will not be sent to SQL?

No, that's not possible.

But I could suggest you another approach that would keep your current design and at the same time will allow you to use an even more concise syntax than

s => s.WFStateCode == ((char)ClassX.eStateCode.Running).ToString())

Right after each enum declare another static class with static readonly fields containing the string codes of the enum members. Something like this:

public enum eStateCode
{
    Running = 'R',
    Terminated = 'T',
    Cancelled = 'C',
}

public static class DbStateCode
{
    public static readonly string Running = ((char)eStateCode.Running).ToString();
    public static readonly string Terminated = ((char)eStateCode.Terminated).ToString();
    public static readonly string Cancelled = ((char)eStateCode.Cancelled).ToString();
}

Yes, that requires some additional effort. But it's one time effort and once you have just several enums like this I think it's worth enough. Because then you can use simply:

s => s.WFStateCode == ClassX.DbStateCode.Running

Unfortunately, this is one of the many shortfalls of Entity Framework. If your column is char(1) or nchar(1) in SQL Server, EF should be able to map it to a char property, and therefore your statement could be:

s => s.WFStateCode == (char)ClassX.eStateCode.Running

However, this is not supported. Here is a work-around:

Make your column an integral type.

alter table [YourTable] add [WFStateId] int not null default 1
update [YourTable] set [WFStateId] = case([WFStateCode])
when 'R' then 1
when 'T' then 2
else 3 end

I know this makes it more difficult to read for someone running a select on the database, but this can be alleviated by creating a new table to join to:

create table [WFState](
[Id] int not null identity primary key,
[Name] nvarchar(30) not null)
insert [WFState] values
('Running'),
('Terminated'),
('Cancelled')

To keep up good database design practices, add a foreign key:

alter table [YourTable] add constraint [FK_YourTable_WFState] foreign key([WFStateId]) references WFState([Id])

Then you can happily change your 'WFStateCode' property to make it type ClassX.eStateCode and change your enum definition:

public enum eStateCode
{
    Running = 1,
    Terminated = 2,
    Cancelled = 3,
}

Now you will be able to freely query against it like this:

s => s.WFStateCode == ClassX.eStateCode.Running

I understand this does not answer your question about mapping a string field to enum, but this workaround will improve your system in two ways:

1: Takes away the magic string anti-pattern (What if a user doesn't know that 'R' means running, etc.)

2: Adds integrity to your database. Now no one can break your system by assigning an invalid status to your table, EG set [WFStatusCode] = 'X'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM