Entity Framework LinqKit dynamic where predicate over related data - entity-framework

The code is in Alpha version, don't have validations or error management ... It would be included later.
I have a very simple model with two related entities: Location and Country:
public class UNCountry
{
public int UNCountryId { get; set; }
[Required]
[MaxLength(2, ErrorMessage = "The field {0} only can contains a maximum of {1} characters lenght.")]
[RegularExpression("[A-Z][A-Z]", ErrorMessage = "The field {0}, This code is formed by Two(2) capital letters")]
public string Code { get; set; }
[Required]
[MaxLength(255, ErrorMessage = "The field {0} only can contains a maximum of {1} characters lenght.")]
public string Name { get; set; }
}
public class UNLocation
{
public int UNLocationId { get; set; }
[MaxLength(1, ErrorMessage = "The field {0} only can contains a maximum of {1} characters lenght.")]
public string UNChangeType { get; set; }
[Required]
[MaxLength(5, ErrorMessage = "The field {0} only can contains a maximum of {1} characters lenght.")]
[RegularExpression("[A-Z]{5}", ErrorMessage = "The field {0}, This code is formed by 5 capital letters")]
public string Code { get; set; }
[Required]
[MaxLength(255, ErrorMessage = "The field {0} only can contains a maximum of {1} characters lenght.")]
public string Name { get; set; }
public int UNCountryId { get; set; }
public UNCountry UNCountry { get; set; }
}
I have an app that list all the locations and the user can filter by any column, this is a generic Component View in Angular and I want to have all the Column Filters be Dynamic. The filters are passed in JsonFilters Parameter as an array
[{name:"nameOfField1", value:"valueOfField1"},...],
then I construct a predicate with LinqKit and PredicateBuilder, and use the predicate in the query .Where(predicate). It works perfectly.
Here is the initial code in the controller
var validFilter = new PaginationFilter(
pF.PageNumber, pF.PageSize, pF.JsonFilters
);
var p = validFilter.CreateDynamicSearch<UNLocation>();
//var b = PredicateBuilder.New<UNLocation>(true);
//b = b.And(c => c.UNCountry.Name.Contains("Col"));
var pagedData = await _context.UNLocations
.Include(c => c.UNCountry)
.Where(p)
//.Where(d => d.UNCountry.Name.Contains("Col"))
.OrderBy(x => x.UNLocationId)
.Skip((validFilter.PageNumber - 1) * validFilter.PageSize)
.Take(validFilter.PageSize)
.ToListAsync();
var totalRecords = await _context.UNLocations.CountAsync();
return Ok(new PagedResponse<List<UNLocation>>(pagedData, validFilter.PageNumber, validFilter.PageSize,totalRecords));
I pass the fieldname and the value in the filters parameter, and all works well, but when I have a filter over the name of the country (the field name is UNCountry.Name) the predicate fails using this name of field over UNLocations, it fails in the function that constructs the predicate, but if I use in the Where directly (The commented line //.Where(d => d.UNCountry.Name.Contains("Col")) in the previous code) It Works, I could be solve the problem like this, but it would not be dynamic for other Views with more related data fields in query.
Here is the predicate generation function:
public Expression<Func<T, bool>> CreateDynamicSearch<T>()
{
var predicate = PredicateBuilder.New<T>(true);
foreach (ReqFilter filter in this.Filters)
{
var columnFilter = PredicateBuilder.New<T>(false);
var param = Expression.Parameter(typeof(T), "a");
string[] filterParts = filter.Name.Split(".");
string filterName = filterParts[0];
if (filterParts.Length == 1)
{
var prop = Expression.Property(param, filterName);
var call = Expression.Call(prop, "Contains", new Type[0], Expression.Constant(filter.Value));
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));
predicate = predicate.And(columnFilter);
}
else
{
var prop = Expression.Property(param, filter.Name);
var call = Expression.Call(prop, "Contains", new Type[0], Expression.Constant(filter.Value));
columnFilter = columnFilter.Or(Expression.Lambda<Func<T, bool>>(call, param));
predicate = predicate.And(columnFilter);
}
}
return predicate;
}
This code works well if i pass the filters with the normal fields of the UNLocation, like code or Name byExample [{code:"MIA"},name:"MI"], but the predicate constructor fails if i send the column name of the related UNCountry Name byExample [{UNCountry.Name:"United States"}], I repeat It works if I write the Where in Design Time code, but i need to have mor flexybility, and generate in predicate. It is possible or how can achieve this ? Thanks in advance, excuse my English.

Related

i:nill="true" appears in response

I have a model -
public class EmployeeModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Designation { get; set; }
public double? Salary { get; set; }
}
and a LINQ method syntax like -
public List<EmployeeModel> GetEmployees()
{
using (var DbCon = new OfficeEntities())
{
var result = DbCon.Employee.Select(x => new EmployeeModel()
{
Id = x.Id,
Name = x.Name
//Salary = x.Salary,
//Designation = x.Designation
})
.ToList();
return result;
}
}
I have commented out salary and designation but even though it prints with
key : salary and for value i:null="true" why
result comes like this
<EmployeeModel>
<Designation i:nil="true"/>
<Id>1</Id>
<Name>Sulochana </Name>
<Salary i:nil="true"/>
</EmployeeModel>
Even though commented/removed the parameters in the query, why it is appearing in the result. Kindly help
Because you didn't include those properties in the projection.
This has a special meaning for the client to interpret, it's not that these fields have a null value, these fields we're not included in the projection, so their values are indeterminate.
If the client has requesting a projection with $select=Id,Name then these fields would not have been included at all, but because the client is expecting all the fields this is how the API expresses to the client that the fields we're deliberately omitted, but to satisfy the return contract the fields must be provided in some form.
This answer is assuming OP is writing an OData service, but the same concept applies with Linq to Entities in general. If the Linq expression is projecting into a model type, but not including certain fields, then those fields will have an uninitialized value on the model instances that are projected out.
You are creating an EmployeeModel object in the Select projection. Hence, Salary and Designation are being initialized with their default values, even though you didn't set values for them. Then after serializing all properties are present in the result.
If you are expecting only Id and Name in the output/result, then define a type in that shape -
public class EmployeeInfo
{
public int Id { get; set; }
public string Name { get; set; }
}
and create an object of that type in the projection -
var result = DbCon.Employee.Select(x => new EmployeeInfo()
{
Id = x.Id,
Name = x.Name
})
.ToList();

Improve navigation property names when reverse engineering a database

I'm using Entity Framework 5 with Visual Studio with Entity Framework Power Tools Beta 2 to reverse engineer moderately sized databases (~100 tables).
Unfortunately, the navigation properties do not have meaningful names. For example, if there are two tables:
CREATE TABLE Contacts (
ContactID INT IDENTITY (1, 1) NOT NULL,
...
CONSTRAINT PK_Contacts PRIMARY KEY CLUSTERED (ContactID ASC)
}
CREATE TABLE Projects (
ProjectID INT IDENTITY (1, 1) NOT NULL,
TechnicalContactID INT NOT NULL,
SalesContactID INT NOT NULL,
...
CONSTRAINT PK_Projects PRIMARY KEY CLUSTERED (ProjectID ASC),
CONSTRAINT FK_Projects_TechnicalContact FOREIGN KEY (TechnicalContactID)
REFERENCES Contacts (ContactID),
CONSTRAINT FK_Projects_SalesContact FOREIGN KEY (SalesContactID)
REFERENCES Contacts (ContactID),
...
}
This will generate classes like this:
public class Contact
{
public Contact()
{
this.Projects = new List<Project>();
this.Projects1 = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> Projects { get; set; }
public virtual ICollection<Project> Projects1 { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact Contact { get; set; }
public virtual Contact Contact1 { get; set; }
}
I see several variants which would all be better than this:
Use the name of the foreign key: For example, everything after the last underscore (FK_Projects_TechnicalContact --> TechnicalContact). Though this probably would be the solution with the most control, this may be more difficult to integrate with the existing templates.
Use the property name corresponding to the foreign key column: Strip off the suffix ID (TechnicalContactID --> TechnicalContact)
Use the concatenation of property name and the existing solution: Example TechnicalContactIDProjects (collection) and TechnicalContactIDContact
Luckily, it is possible to modify the templates by including them in the project.
The modifications would have to be made to Entity.tt and Mapping.tt. I find it difficult due to the lack of intellisense and debug possibilities to make those changes.
Concatenating property names (third in above list) is probably the easiest solution to implement.
How to change the creation of navigational properties in Entity.tt and Mapping.tt to achieve the following result:
public class Contact
{
public Contact()
{
this.TechnicalContactIDProjects = new List<Project>();
this.SalesContactIDProjects = new List<Project>();
}
public int ContactID { get; set; }
// ...
public virtual ICollection<Project> TechnicalContactIDProjects { get; set; }
public virtual ICollection<Project> SalesContactIDProjects { get; set; }
}
public class Project
{
public Project()
{
}
public int ProjectID { get; set; }
public int TechnicalContactID { get; set; }
public int SalesContactID { get; set; }
// ...
public virtual Contact TechnicalContactIDContact { get; set; }
public virtual Contact SalesContactIDContact { get; set; }
}
There a few things you need to change inside the .tt file. I choose to use the third solution you suggested but this requires to be formatted like FK_CollectionName_RelationName. I split them up with '_' and use the last string in the array.
I use the RelationName with the ToEndMember property to create a property name. FK_Projects_TechnicalContact will result in
//Plularized because of EF.
public virtual Contacts TechnicalContactContacts { get; set; }
and your projects will be like this.
public virtual ICollection<Projects> SalesContactProjects { get; set; }
public virtual ICollection<Projects> TechnicalContactProjects { get; set; }
Now the code you may ask. Ive added 2 functions to the CodeStringGenerator class in the T4 file. One which builds the propertyName recieving a NavigationProperty. and the other one generating the code for the property recieving a NavigationProperty and the name for the property.
//CodeStringGenerator class
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty)
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = ForeignKeyName[ForeignKeyName.Length-1] + navigationProperty.ToEndMember.Name;
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
name,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
If you place the above code in the class you still need to change 2 parts. You need to find the place where the constructor part and the navigation property part are being build up of the entity. In the constructor part (around line 60) you need to replace the existing code by calling the method GetPropertyNameForNavigationProperty and passing this into the escape method.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
this.<#=code.Escape(propName)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
And in the NavigationProperties part (around line 100) you also need to replace the code with the following.
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty);
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty, propName)#>
<#
I hope this helps and you can always debug the GetPropertyNameForNavigationProperty function and play a little with the naming of the property.
Building on BikeMrown's answer, we can add Intellisense to the properties using the RelationshipName that is set in MSSQL:
Edit model.tt in your VS Project, and change this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
to this:
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
}
#>
/// <summary>
/// RelationshipName: <#=code.Escape(navigationProperty.RelationshipType.Name)#>
/// </summary>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
}
}
Now when you start typing a property name, you get a tooltip like this:
It's probably worth noting that if you change your DB model, the properties may find themselves pointing at different DB fields because the EF generates navigation property names based on their respective DB field name's alphabetic precedence!
Found this question/answer very helpful. However, I didn't want to do as much as Rikko's answer. I just needed to find the column name involved in the NavigationProperty and wasn't seeing how to get that in any of the samples (at least not without an edmx to pull from).
<#
var association = (AssociationType)navProperty.RelationshipType;
#> // <#= association.ReferentialConstraints.Single().ToProperties.Single().Name #>
The selected answer is awesome and got me going in the right direction for sure. But my big problem with it is that it took all of my already working navigation properties and appended the base type name to them, so you'd end up with with things like the following.
public virtual Need UnitNeed { get; set;}
public virtual ShiftEntered UnitShiftEntered {get; set;}`
So I dug into the proposed additions to the .tt file and modified them a bit to remove duplicate type naming and clean things up a bit. I figure there's gotta be someone else out there that would want the same thing so I figured I'd post my resolution here.
Here's the code to update within the public class CodeStringGenerator
public string GetPropertyNameForNavigationProperty(NavigationProperty navigationProperty, string entityname = "")
{
var ForeignKeyName = navigationProperty.RelationshipType.Name.Split('_');
var propertyName = "";
if (ForeignKeyName[ForeignKeyName.Length-1] != entityname){
var prepender = (ForeignKeyName[ForeignKeyName.Length-1].EndsWith(entityname)) ? ReplaceLastOccurrence(ForeignKeyName[ForeignKeyName.Length-1], entityname, "") : ForeignKeyName[ForeignKeyName.Length-1];
propertyName = prepender + navigationProperty.ToEndMember.Name;
}
else {
propertyName = navigationProperty.ToEndMember.Name;
}
return propertyName;
}
public string NavigationProperty(NavigationProperty navigationProperty, string name)
{
var endType = _typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType());
var truname = name;
if(navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many){
if(name.Split(endType.ToArray<char>()).Length > 1){
truname = ReplaceLastOccurrence(name, endType, "");
}
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} {1} {2} {{ {3}get; {4}set; }}",
AccessibilityAndVirtual(Accessibility.ForProperty(navigationProperty)),
navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
truname,
_code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
_code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}
public static string ReplaceLastOccurrence(string Source, string Find, string Replace)
{
int place = Source.LastIndexOf(Find);
if(place == -1)
return Source;
string result = Source.Remove(place, Find.Length).Insert(place, Replace);
return result;
}
and here's the code to update within the model generation,
update both occurrences of this:
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty)
to this
var propName = codeStringGenerator.GetPropertyNameForNavigationProperty(navigationProperty, entity.Name);

Error with List<T>, says "Using the generic type 'System.Collections.Generic.List' requires 1 type arguments"

Working on the shopping part of thisnd smeting strange is happening. When I go to populate my lixt I get the error:
Using the generic type
'System.Collections.Generic.List'
requires 1 type arguments
I have done this on several other classes but none give this error. Here's the class causing the error:
StoreItem.cs
public class StoreItemViewModel
{
public StoreItemViewModel()
{
this.StoreItems = GetStoreItemList(null);
}
private SelectList GetStoreItemList(string selectedValue)
{
List<StoreItems> list = new List<StoreItems>();
IRepository<GodsCreationTaxidermy.Data.StoreItem> storeItems = ObjectFactory.GetInstance<IRepository<StoreItem>>();
foreach (StoreItem item in storeItems.GetAll())
{
List.Add(new StoreItems <= error on this line
{
Key = item.Key,
CategoryKey = item.CategoryKey,
ItemName = item.ItemName,
ItemDescription = item.ItemDescription,
ItemPriced = item.ItemPrice,
DatePosted = item.DatePosted,
});
}
return new SelectList(list, "StoreItemID", "StoreItemName", selectedValue);
}
[UIHint("StoreItems")]
public SelectList StoreItems { get; private set; }
[Required(ErrorMessage = "Store Item is required")]
public string StoreItem { get; set; }
}
I can show other classes that do this exact thing (maybe a new set f eyes can here) and here's one of them:
AnimalList.cs
public class AnimalsList
{
public AnimalsList()
{
this.Animals = GetanimalList(null);
}
private SelectList GetanimalList(string selectedValue)
{
List<Animal> list = new List<Animal>();
IRepository<AnimalList> animals = ObjectFactory.GetInstance<IRepository<AnimalList>>();
foreach (AnimalList animal in animals.GetAll())
{
list.Add(new Animal
{
AnimalId = animal.animal_id,
AnimalName = animal.animal_name,
IsBird = Convert.ToBoolean(animal.is_bird),
MountType = animal.mount_type
});
}
return new SelectList(list, "AnimalId", "AnimalName", selectedValue);
}
[UIHint("Animal")]
public SelectList Animals { get; private set; }
[Required(ErrorMessage = "Animal is required")]
public string Animal { get; set; }
}
Can someone tell me wat I'm doing wrong here. I've seen a lot of very obscure errors the past few days (most I resolved) but others I had to ask for help.If you nee more code then ust let me know :)
List is capitalized, so you're referencing the Class instead of the instance "list".
You should consider changing the variable name to something useful, and less vague or error prone so you can catch these faster, or prevent them entirely.
In the code you see List.Add, that's not going to work. Once I made it lower case all is well.

ASP NET MVC How to call Count on an attribute then print out a sorted list?

Say I had a class:
public class Post
{
public int PostId { get; set; }
public string Topic { get; set; }
public int UserId { get; set; }
[StringLength(5000)]
public string Body { get; set; }
public DateTime DateCreated { get; set; }
public string Name { get; set; }
public int Votes { get; set; }
}
And for each post, a user could input a topic. for example, if the topics were "Red" "Green" "Blue" and "Yellow", how could I create a list based on how many times those were used?
An example output:
Red | 70
Blue | 60
Green | 40
Yellow| 35
EDIT: How come this doesn't work and gives me an error where I cannot implicitly convert the type?
public List<string> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
EDIT 2:
So I tried your solution out Dustin, and I'm getting an error. This is what I used:
public IEnumerable<IGrouping<string,int>> GetPosts()
{
var posts = from p in db.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
return posts.ToList();
}
This is giving me an error under posts.ToList():
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable>'. An explicit conversion exists (are you missing a cast?)
To create the grouping you create an anonymous type such as:
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
Then to work with the date, lets say iterate over it:
foreach(var p in posts)
{
Response.Write(String.Format("{0} - {1}", p.Topic, p.Count));
}
You must create a new type if you do a projection and return it form method!
public class MyCounts
{
public string Topic { get; set; }
public int Count { get; set; }
}
public List<MyCounts> GetPopularTopics(int count)
{
var posts = from p in db.Posts
group p by p.Topic into myGroup
select new MyCounts
{
Topic = myGroup.Key,
Count = myGroup.Count()
};
return posts.ToList();
}
The problem is that you need to use an non anonymous type for your return value.
This query creates an IEnumerable of anonymous types.
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
It's the select new statement that creates the anonymous objects.
What you need to do is to create something that is non anonymous - an object that can be shared within and outside this method.
Like this:
public IEnumerable<TopicAndCount> GetPosts()
{
var posts = from p in context.Posts
group p by p.Topic into topicCounts
select new TopicAndCount
{
Topic = topicCounts.Key,
Count = topicCounts.Count()
};
}
Note the select new TopicAndCount statement and the return value of the enclosing method.
That will solve your problem.

Parse string to poco class enum in linq query MVC 2.0

I have the following enum and POCO class
public enum Gender
{
Male,
Female,
Unknown
}
public class Person
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public Gender? Gender { get; set; }
}
I would like to perform a "get all people" query in my repository such that it would look something like this:
return from p in _db.People
select new Model.Person
{
PersonId = p.PersonId,
LastName = p.LastName,
FirstName = p.FirstName,
Gender = p.Gender,
};
Unfortunately I get an error "Cannot implicitly convert type 'string' to 'Model.Gender'"
I would like to convert the string which is being queried from the entity framework to my Gender enum and assign it to my POCO class.
Enums are not supported in Entity Framework. There is a workaround by Alex James, but it's quite involved.
Instead, i prefer to do this:
public enum Gender : byte
{
Male = 1,
Female,
Unknown
}
public class Person
{
public int PersonId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public byte Gender { get; set; } // this is the EF model property
public Gender GenderType // this is an additional custom property
{
get { return (Gender) Gender; }
set { Gender = (byte)value; }
}
}
It's basically a hook/wrapper for the actual value. In your database, store Gender as a tinyint (which maps to byte on the conceptual side).
Then you can use a byte enum to map to and from the model property:
return from p in _db.People
select new Model.Person
{
PersonId = p.PersonId,
LastName = p.LastName,
FirstName = p.FirstName,
Gender = p.Gender, // sets byte
};
But then if you access that ViewModel, because your setting the byte field for Gender, you will also have access to the enum property GenderType.
Does that solve your problem?
The Entity Framework that I am familiar with does not provide support for enums. EF uses your query expression to create an SQL statement that it then sends to the server, if it cannot create the SQL equivalent of some operation it will throw a NotSupportedException for that operation. If you are expecting to return a small set of data you can separate from the Entity Framework by creating an object in memory using the ToArray method.
var myEntities = (from entity in _db.Entities
where /* condition */
select entity)
.ToArray();
This will create a sequence of entities in memory. Any further query statements will then be in the realm of LINQ to Objects which allows parsing of strings into enums:
return from myEntity in myEntities
select new MyDataContract
{
ID = myEntity.ID,
Gender g = (Gender)Enum.Parse(typeof(Gender), myEntity.Gender, true)
};
Or you could even break it out into a foreach loop:
List<MyDataContract> myDataContracts = new List<MyDataContract>();
foreach (var myEntity in myEntities)
{
var dataContract = new MyDataContract { ID = myEntity.ID };
if (Enum.IsDefined(typeof(Gender), myEntity.Gender))
dataContract.Gender = (Gender)Enum.Parse(typeof(Gender), myEntity.Gender, true);
myDataContracts.Add(dataContract);
}
return myDataContracts.AsEnumerable();
if (Enum.IsDefined(typeof(Gender), genderstring))
Gender g = (Gender) Enum.Parse(typeof(Gender), genderstring, true);
else
//Deal with invalid string.
try
Gender = p.Gender != null ? (Gender)Enum.Parse(typeof(Gender), p.Gender) : (Gender?)null;
To parse the string as one of the enums
here's a workaround but it means changing your nice and clean POCO
http://blogs.msdn.com/b/alexj/archive/2009/06/05/tip-23-how-to-fake-enums-in-ef-4.aspx