Create database index with Entity Framework - c#

Say I have the following model:
[Table("Record")]
public class RecordModel
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Display(Name = "Record Id")]
public int RecordId { get; set; }
[StringLength(150)]
public string Name { get; set; }
[Required]
[StringLength(15)]
public string IMEI { get; set; }
}
Is it possible to add an index to the IMEI column through using an attribute, data annotation, or something from the model?

EF Core 5
In EF Core 5, the Index attribute should be placed on the class.
See: MSDN
[Index(nameof(Url))]
public class Post
{
public int PostId { get; set; }
public string Url { get; set; }
public string Title { get; set; }
public DateTime PublishedOn { get; set; }
}
or revert to the fluent syntax for more advanced option:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Post>()
.HasIndex(p => p.Url)
.IncludeProperties(
p => new { p.Title, p.PublishedOn });
}
EF 6.1
Since the release of EF 6.1. (March 17th, 2014) there is indeed an [Index] attribute available.
Functionality as:
[Index("IMEIIndex", IsUnique = true)]
public string IMEI { get; set; }
comes out of the box.
PS: other properties are Order and IsClustered.
According to this link: http://blogs.msdn.com/b/adonet/archive/2014/02/11/ef-6-1-0-beta-1-available.aspx
It will be available in EF 6.1 as a standard DataAnnotation attribute.
IndexAttribute allows indexes to be specified by placing an [Index] attribute on a property (or properties) in your Code First model. Code First will then create a corresponding index in the database.

According to this link: Creating Indexes via Data Annotations with Entity Framework 5.0
you should write some kind of extension code:
using System;
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
public class IndexAttribute : Attribute
{
public IndexAttribute(string name, bool unique = false)
{
this.Name = name;
this.IsUnique = unique;
}
public string Name { get; private set; }
public bool IsUnique { get; private set; }
}
and the second class:
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Reflection;
public class IndexInitializer<T> : IDatabaseInitializer<T> where T : DbContext
{
private const string CreateIndexQueryTemplate = "CREATE {unique} INDEX {indexName} ON {tableName} ({columnName})";
public void InitializeDatabase(T context)
{
const BindingFlags PublicInstance = BindingFlags.Public | BindingFlags.Instance;
foreach (var dataSetProperty in typeof(T).GetProperties(PublicInstance).Where(
p => p.PropertyType.Name == typeof(DbSet<>).Name))
{
var entityType = dataSetProperty.PropertyType.GetGenericArguments().Single();
TableAttribute[] tableAttributes = (TableAttribute[])entityType.GetCustomAttributes(typeof(TableAttribute), false);
foreach (var property in entityType.GetProperties(PublicInstance))
{
IndexAttribute[] indexAttributes = (IndexAttribute[])property.GetCustomAttributes(typeof(IndexAttribute), false);
NotMappedAttribute[] notMappedAttributes = (NotMappedAttribute[])property.GetCustomAttributes(typeof(NotMappedAttribute), false);
if (indexAttributes.Length > 0 && notMappedAttributes.Length == 0)
{
ColumnAttribute[] columnAttributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), false);
foreach (var indexAttribute in indexAttributes)
{
string indexName = indexAttribute.Name;
string tableName = tableAttributes.Length != 0 ? tableAttributes[0].Name : dataSetProperty.Name;
string columnName = columnAttributes.Length != 0 ? columnAttributes[0].Name : property.Name;
string query = CreateIndexQueryTemplate.Replace("{indexName}", indexName)
.Replace("{tableName}", tableName)
.Replace("{columnName}", columnName)
.Replace("{unique}", indexAttribute.IsUnique ? "UNIQUE" : string.Empty);
context.Database.CreateIfNotExists();
context.Database.ExecuteSqlCommand(query);
}
}
}
}
}
}
After it you can use your index this way:
[Required]
[Index("IMEIIndex", unique: true)]
[StringLength(15)]
public string IMEI { get; set; }

Related

DynamoDB - How to implement Optimistic Locking using ServiceStack.Aws

Currently, I am using ServiceStack.Aws v5.9.0 to communicate with DynamoDB. I have used PutItem for both creating and updating an item without anticipating data loss in case of concurrency handling.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
}
public class CustomerDynamo
{
private readonly IPocoDynamo db;
//Constructor
public CustomerDynamo()
{
var dynamoClient = new AmazonDynamoDBClient(_region);
var entityType = typeof(Customer);
var tableName = entityType.Name;
entityType.AddAttributes(new AliasAttribute(name: tableName));
db = new PocoDynamo(dynamoClient) { ConsistentRead = true }.RegisterTable(tableType: entityType);
}
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
db.PutItem(customer);
return customer;
}
}
The above Update method is called in every service/async task that needs to update the data of the customer.
Refer to this article of AWS I decided to implement the Optimistic Locking to save my life from the issue of concurrency requests.
https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBContext.VersionSupport.html
Assume that the VersionNumber will be the key for Optimistic Locking. So I added the VersionNumber into the Customer model.
public class Customer
{
[HashKey]
public int CustomerId { get; set; }
[AutoIncrement]
public int SubId { get; set; }
public string CustomerType { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
...//and hundreds of fields here
[DynamoDBVersion]
public int? VersionNumber { get; set; }
}
The result is VersionNumber not updated while it should be automatically incremented. I think it is just because the PutItem will override the whole existing item. Is this correct?
I think I need to change from PutItem to UpdateItem in the Update method. The question is how can I generate the expression dynamically to be used with the UpdateItem?
Thanks in advance for any help!
Updates:
Thanks #mythz for the useful information about DynamoDBVersion attribute. Then I tried to remove the DynamoDBVersion and using the UpdateExpression of PocoDynamo as below
public Customer Update(Customer customer)
{
customer.ModifiedDate = DateTime.UtcNow;
var expression = db.UpdateExpression<Customer>(customer.CustomerId).Set(() => customer);
expression.ExpressionAttributeNames = new Dictionary<string, string>()
{
{ "#Version", "VersionNumber" }
};
expression.ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{ ":incr", new AttributeValue { N = "1" } },
{ ":zero", new AttributeValue { N = "0" } }
};
expression.UpdateExpression = "SET #Version = if_not_exists(#Version, :zero) + :incr";
if (customer.VersionNumber.HasValue)
{
expression.Condition(c => c.VersionNumber == customer.VersionNumber);
}
var success = db.UpdateItem(expression);
}
But the changes are not saved except the VersionNumber
The [DynamoDBVersion] is an AWS Object Persistence Model attribute for usage with AWS's DynamoDBContext not for PocoDynamo. i.e. the only [DynamoDB*] attributes PocoDynamo utilizes are [DynamoDBHashKey] and [DynamoDBRangeKey] all other [DynamoDB*] attributes are intended for AWS's Object Persistence Model libraries.
When needed you can access AWS's IAmazonDynamoDB with:
var db = new PocoDynamo(awsDb);
var awsDb = db.DynamoDb;
Here are docs on PocoDynamo's UpdateItem APIs that may be relevant.

Raw queries with overridden column names

I'm trying to retrieve some entities using Entity Framework by querying an XML column. Entity Framework doesn't support this so I had to use raw SQL.
var people = context.People.SqlQuery("SELECT * FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21").AsQueryable().AsNoTracking();
My person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
This should work, however, it falls over when trying to map it back to an object. Specifically, it's falling over on the Age property, which has it's column name overridden to "YearsSinceBirth".
'The data reader is incompatible with the specified
'MyProject.CodeBase.DataModel.DbEntities.Person'. A member of the
type, 'Age', does not have a corresponding column in the data reader
with the same name.'
I'm guessing that Entity Framework doesn't map database column names to object property names and therefore is expecting the column to be named 'Age' rather than 'YearsSinceBirth'.
I don't want to have to list each column and their mapping in the SQL query (like SELECT YearsSinceBirth As Age) as the actual project I'm working on which has this column has a lot more columns and that would mean this query would break every time the schema changed (kinda defeating the purpose of Entity Framework).
If this is EF Core, your problem is not that SqlQuery() doesn't support mapping column names (it does). Rather your problem is that your table doesn't contain a column called YearsSinceBirth, and you are returning 'select *'.
If you have a column called YearsSinceBirth, this works fine. Although you will be retrieving the value in the YearsSinceBirth column, not the value in the XML document. EG
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//using Microsoft.Samples.EFLogging;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;
namespace EFCore2Test
{
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
[Column("YearsSinceBirth")]
public int Age { get; set; }
[Column(TypeName = "xml")]
public string DataXML { get; set; }
}
public class Location
{
public string LocationId { get; set; }
}
public class Db : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<Location> Locations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(local);Database=EFCoreTest;Trusted_Connection=True;MultipleActiveResultSets=true");
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
using (var db = new Db())
{
db.Database.EnsureDeleted();
//db.ConfigureLogging(s => Console.WriteLine(s));
db.Database.EnsureCreated();
var p = new Person()
{
Name = "joe",
Age = 2,
DataXML = "<Properties><Age>21</Age></Properties>"
};
db.People.Add(p);
db.SaveChanges();
}
using (var db = new Db())
{
var people = db.People.FromSql("SELECT * FROM [People] WHERE [DataXML].value('(/Properties/Age)[1]', 'int') = 21").AsNoTracking().ToList() ;
Console.WriteLine(people.First().Age);
Console.ReadLine();
}
Console.WriteLine("Hit any key to exit");
Console.ReadKey();
}
}
}
You can use a pattern similar to this to project entity attributes from an XML or JSON column:
public class Person
{
private XDocument xml;
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public int Age
{
get
{
return int.Parse(xml.Element("Properties").Element("Age").Value);
}
set
{
xml.Element("Properties").Element("Age").Value = value.ToString();
}
}
[Column(TypeName = "xml")]
public string DataXML
{
get
{
return xml.ToString();
}
set
{
xml = XDocument.Parse(value);
}
}
}
You can dynamically create select query with aliases, if they needed, with the help of reflection and ColumnAttribute checking:
public string SelectQuery<T>() where T : class
{
var selectQuery = new List<string>();
foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
var attr = prop.GetAttribute<ColumnAttribute>();
selectQuery.Add(attr != null ? $"{attr.Name} as {prop.Name}" : prop.Name);
}
return string.Join(", ", selectQuery);
}
Usage:
var people = context.People.SqlQuery($"SELECT {SelectQuery<Person>()} FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21")
.AsQueryable().AsNoTracking();

LINQ to SQL "The INSERT statement conflicted with the FOREIGN KEY constraint "FK_Child_Parent""

I'm getting that SqlException while trying to insert data into a table using navigation property. Seems like the foreign key is not updated automatically and has a default value 0. The same time it works with auto-generated classes. I don't want to use auto-generated models.
How do I have to modify my models to make it work? Is it about INotifyPropertyChanging or INotifyPropertyChanged?
Thanks.
class Program
{
static void Main(string[] args)
{
var db = new DbContext();
var p = db.Parents.Single(x => x.Id == 2);
p.Children.Add(new Child_ { Name = "P2_Child_1" });
db.SubmitChanges();
}
}
class DbContext : DataContext
{
public DbContext() : base(#"Data Source=.\SQLEXPRESS;Initial Catalog=TestDb;Integrated Security=True;") { }
public Table<Parent_> Parents { get { return GetTable<Parent_>(); } }
public Table<Child_> Children { get { return GetTable<Child_>(); } }
}
[Table(Name = "Parents")]
class Parent_
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int Id { get; set; }
[Column]
public string Name { get; set; }
[Association(OtherKey = "ParentId")]
public EntitySet<Child_> Children { get; set; }
}
[Table(Name = "Children")]
class Child_
{
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
public int Id { get; set; }
[Column]
public int ParentId { get; set; }
[Column]
public string Name { get; set; }
[Association(IsForeignKey = true, ThisKey = "ParentId", OtherKey = "Id")]
public Parent_ Parent { get; set ; }
}
To fix this I had to manually code the Insert method on an inherited DataContext:
Public Class ClientDataContext : Inherits DataContext
...
Public Sub InsertYourEntity(instance As YourEntity)
With instance
ExecuteDynamicInsert(instance)
If TypeOf instance Is YourEntity then
For each child As YourChildEntity In instance.YourChildEntities
child.YourForeignKeyId = .Id
Next
End If
End With
End Sub
LinqToSql appears to use this method via reflection. So once coded resharper doesn't think its used, but integration tests hit it.

Entity Framework 5 - LINQ syntax error

I have the following code where I am creating a IList that I need to filter by the data in another list called List. The locations list represents the locations a user is allowed to view based on their permissions. I am new to LINQ and am confused with error I get (C# Unknown method "Where(?)" of "System.Ling.IQueryable". I have tried various syntax arrangement using either Contains() and Any() or both to no avail. I feel like it's something very basic that I don't understand about doing this. Here is the code:
----- users locations
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
namespace Decking.Models
{
public class locations
{
[Key]
public string org_id { get; set; }
}
}
///////// here is the view model
using System.ComponentModel.DataAnnotations;
using System;
namespace Decking.Models
{
public class InventoryViewModel
{
[Key]
public int id { get; set; }
public DateTime metric_dt { get; set; }
public int? item_id { get; set; }
public int? loc_type_id { get; set; }
public string trlr_nbr { get; set; }
public string user_id { get; set; }
public string org_id { get; set; }
public Double numerator { get; set; }
//these are the child entities
[UIHint("ClientItem")]
public ItemViewModel Items
{
get;
set;
}
[UIHint("ClientLocTypes")]
public LocTypesViewModel LocTypes
{
get;
set;
}
[UIHint("ClientOrgsByUser")]
public OrgsByUserViewModel OrgsByUser
{
get;
set;
}
}
}
///////// code to populate the view model
public IList<InventoryViewModel> GetAll(List<locations> locs)
{
IList<InventoryViewModel> result = new List<InventoryViewModel>();
result = entities.inventory.Select(inventory => new
InventoryViewModel
{
id = inventory.id,
metric_dt = inventory.metric_dt,
item_id = inventory.item_id,
loc_type_id = inventory.loc_type_id,
trlr_nbr = inventory.trlr_nbr,
org_id = inventory.org_id,
numerator = inventory.numerator,
user_id = inventory.user_id,
Items = new ItemViewModel()
{
item_id = inventory.items.item_id,
item_desc = inventory.items.item_desc,
},
LocTypes = new LocTypesViewModel()
{
loc_type_id = inventory.loc_types.loc_type_id,
loc_desc = inventory.loc_types.loc_desc,
},
OrgsByUser = new OrgsByUserViewModel()
{
user_id = inventory.user_id,
//mgr_emp_nbr = inventory.mgr,
org_id = inventory.org_id,
},
}).Where(e => e.metric_dt == DateTime.Today && e.org_id
==locs.Any(o=>o.org_id)) // this doesn't work
//}).Where(e => e.metric_dt == DateTime.Today && e.org_id == "SGF") //
this works
.ToList();
return result;
}
Any help you can provide would be greatly appreciated! Thank so much!
The problem is in e.org_id == locs.Any(o=>o.org_id). As I can see in your working example, your org_id is a string.
I guess what you are trying to do is .Where(e => e.metric_dt == DateTime.Today && locs.Any(o=>o.org_id == e.org_id))

How to create a table corresponding to enum in EF6 Code First?

I've followed MSDN on how to handle enumerations in Code First for EF6. It worked, as supposed to but the field in the created table that refers to the enumerator is a simple int.
I'd prefer a second table to be produced, the values of which would follow the definition of the enumerator in C# code. So, instead of only getting a table corresponding to Department in the example on MSDN, I'd also like to see a second table populated by the items from Faculty.
public enum Faculty { Eng, Math, Eco }
public partial class Department
{
[Key] public Guid ID { get; set; }
[Required] public Faculty Name { get; set; }
}
Researching the issue, I stumbled upon a solution, which suggests creating a table for the enumeration and populating it explicitly by seeding.
It appear to me as a cumbersome approach and a lot of work that should be handled automagically. After all, the system knows what actual values that constitute the enumeration. From DB point of view it's still data rows, just as the entities that I create but from OO aspect, it's not really a data - rather a type (loosely expressed) that can assume a finite and onbeforehand known number of states.
Is the approach of populating the table "manually" recommended?
Since EF doesn't handle it automatically, yes, this is the recommend way.
I suggest some modifications in article that you provided.
Rename your enum
public enum FacultyEnum { Eng, Math, Eco }
Create a class that represent the table
public class Faculty
{
private Faculty(FacultyEnum #enum)
{
Id = (int)#enum;
Name = #enum.ToString();
Description = #enum.GetEnumDescription();
}
protected Faculty() { } //For EF
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string Description { get; set; }
public static implicit operator Faculty(FacultyEnum #enum) => new Faculty(#enum);
public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id;
}
Your model reference the class
public class ExampleClass
{
public virtual Faculty Faculty { get; set; }
}
Create a extension method to get description from enum and seed values
using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
public static class Extensions
{
public static string GetEnumDescription<TEnum>(this TEnum item)
=> item.GetType()
.GetField(item.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault()?.Description ?? string.Empty;
public static void SeedEnumValues<T, TEnum>(this IDbSet<T> dbSet, Func<TEnum, T> converter)
where T : class => Enum.GetValues(typeof(TEnum))
.Cast<object>()
.Select(value => converter((TEnum)value))
.ToList()
.ForEach(instance => dbSet.AddOrUpdate(instance));
}
Add the seed in Configuration.cs
protected override void Seed(Temp.MyClass context)
{
context.Facultys.SeedEnumValues<Faculty, FacultyEnum>(#enum => #enum);
context.SaveChanges();
}
Add the enum table in your DbContext
public class MyClass : DbContext
{
public DbSet<ExampleClass> Examples { get; set; }
public DbSet<Faculty> Facultys { get; set; }
}
Use it
var example = new ExampleClass();
example.Faculty = FacultyEnum.Eng;
if (example.Faculty == FacultyEnum.Math)
{
//code
}
To remember
If you don't add virtual in Faculty property, you must use Include method from DbSet to do Eager Load
var exampleFromDb = dbContext.Examples.Include(x => x.Faculty).SingleOrDefault(e => e.Id == 1);
if (example.Faculty == FacultyEnum.Math)
{
//code
}
If Faculty property is virtual, then just use it
var exampleFromDb = dbContext.Examples.Find(1);
if (example.Faculty == FacultyEnum.Math)
{
//code
}
Based on #Alberto Monteiro answer i've created generic class in case when you have several tables. The notice here is that Id is the type of TEnum. Using it in such way will provide option to use Enum for declaring property type.
public class Question
{
public QuestionTypeEnum QuestionTypeId { get; set; } // field property
public QuestionType QuestionType { get; set; } // navigation property
}
By default Enum using integers, so the db provider will create field with "int" type.
EnumTable.cs
public class EnumTable<TEnum>
where TEnum : struct
{
public TEnum Id { get; set; }
public string Name { get; set; }
protected EnumTable() { }
public EnumTable(TEnum enumType)
{
ExceptionHelpers.ThrowIfNotEnum<TEnum>();
Id = enumType;
Name = enumType.ToString();
}
public static implicit operator EnumTable<TEnum>(TEnum enumType) => new EnumTable<TEnum>(enumType);
public static implicit operator TEnum(EnumTable<TEnum> status) => status.Id;
}
ExceptionHelpers.cs
static class ExceptionHelpers
{
public static void ThrowIfNotEnum<TEnum>()
where TEnum : struct
{
if (!typeof(TEnum).IsEnum)
{
throw new Exception($"Invalid generic method argument of type {typeof(TEnum)}");
}
}
}
Now you just can inherit the EnumTable
public enum QuestionTypeEnum
{
Closed = 0,
Open = 1
}
public class QuestionType : EnumTable<QuestionTypeEnum>
{
public QuestionType(QuestionTypeEnum enumType) : base(enumType)
{
}
public QuestionType() : base() { } // should excplicitly define for EF!
}
Seed the values
context.QuestionTypes.SeedEnumValues<QuestionType, QuestionTypeEnum>(e => new QuestionType(e));
Another possibility, if you want to keep your model simpler, POCO style, use the enum as a property that will be stored as an integer by entity framework.
Then, if you want the "enum tables" to be created and updated in your DB, I recommend using the nuget package https://github.com/timabell/ef-enum-to-lookup and use it in a EF Migration seed method for example:
public enum Shape
{
Square,
Round
}
public class Foo
{
public int Id { get; set; }
public Shape Shape { get; set; }
}
public class MyDbContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
}
using(var context = new MyDbContext())
{
var enumToLookup = new EnumToLookup
{
TableNamePrefix = string.Empty,
NameFieldLength = 50,
UseTransaction = true
};
enumToLookup.Apply(context);
}
This will create the "Shape" table with 2 rows named Square and Round, with the relevant foreign key constraint in the table "Foo"
Excellent #AlbertoMonterio! To get this to work with ASP.NET CORE / EF Core I made a few adjustments to Alberto's solution.
For brevity, only the modifications are shown below:
Create a extension method to get description from enum and seed values
using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using Microsoft.EntityFrameworkCore; //added
using Microsoft.EntityFrameworkCore.Metadata.Builders; //added
public static class Extensions
{
//unchanged from alberto answer
public static string GetEnumDescription<TEnum>(this TEnum item)
=> item.GetType()
.GetField(item.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault()?.Description ?? string.Empty;
//changed
public static void SeedEnumValues<T, TEnum>(this ModelBuilder mb, Func<TEnum, T> converter)
where T : class => Enum.GetValues(typeof(TEnum))
.Cast<object>()
.Select(value => converter((TEnum)value))
.ToList()
.ForEach(instance => mb.Entity<T>().HasData(instance));
}
Add the seed in Configuration.cs
Add Seeding to OnModelCreating of DataContext
protected override void OnModelCreating(ModelBuilder builder)
{
builder.SeedEnumValues<Faculty, EnumEntityRole>(e => e);
}
Another approach that works (and feels simpler to me) in EF Core:
Your Enum
public enum Color
{
Red = 1,
Blue = 2,
Green = 3,
}
Db Tables
public class CustomObjectDto
{
public int ID { get; set; }
// ... other props
public Color ColorID { get; set; }
public ColorDto ColorDto { get; set; }
}
public class ColorDto
{
public Color ID { get; set; }
public string Name { get; set; }
}
Your DbContext
public class Db : DbContext
{
public Db(DbContextOptions<Db> options) : base(options) { }
public DbSet<CustomObjectDto> CustomObjects { get; set; }
public DbSet<ColorDto> Colors { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Seed database with all Colors
foreach (Color color in Enum.GetValues(typeof(Color)).Cast<Color>())
{
ColorDto colorDto = new ColorDto
{
ID = color,
Name = color.ToString(),
};
modelBuilder.Entity<ColorDto>().HasData(colorDto);
}
}
}
In code I basically only use the enum Color (never ColorDto). But it's still nice to have the 'Colors' table with an FK in the 'CustomObjects' table for sql queries and views.
I might be a bit late for the party but I didn't find the answer I was looking for here.
While looking around in the EntityFramework documentation I found the solution, it is the first example in Value Conversions
With this you can make a nice extension method if you want. i.e.
public static void HasEnum<TEntity, TProperty>(this EntityTypeBuilder<TEntity> entityBuilder, Expression<Func<TEntity, TProperty>> propertyExpression)
where TEntity : class
where TProperty : Enum
{
entityBuilder.Property(propertyExpression)
.HasConversion(
v => v.ToString(),
v => (TProperty)Enum.Parse(typeof(TProperty), v)
);
}
Then use it in your OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<YourEntity>()
.HasEnum(e => e.YourProperty);
}
You should add : byte in front of enum declaration :
enum MyFieldEnum : byte{
one = 1,
two = 2,
three = 4
}
In database, you should see TINYINT and no need to casting !
UPDATE: I found a better way that works well in EntityFrameworkCore 5.0.8
Add JsonConverter attributes to your enum
[Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
public enum FacultyEnum
{
[EnumMember(Value = "English Professor")]
Eng,
[EnumMember(Value = "Math Professor")]
Math,
[EnumMember(Value = "Economics Professor")]
Eco
}
Create a class the represents the table
public class Faculty
{
public int Id { get; set; }
public string Name { get; set; }
public FacultyEnum Description { get; set; }
}
Use Fluent API in OnModelCreating in your DbContext to use the enum strings and set check constraints
var enumToString = new EnumToStringConverter<FacultyEnum>();
modelBuilder.Entity<Faculty>(entity =>
{
entity.ToTable(nameof(FacultyMembers));
//convert enums to string
entity.Property(e => e.Description).HasConversion(enumToString);
//build check constraint from enum
var allowedEnumStrings = string.Join(',',
typeof(Faculty).GetMembers()
.Select(x => x.GetCustomAttribute(typeof(EnumMemberAttribute), false)).Where(x => x != null)
.Select(x => $"'{((EnumMemberAttribute)x).Value}'"));
entity.HasCheckConstraint($"CK_{nameof(FacultyMembers)}_{nameof(Faculty.Description)}", $"{nameof(Faculty.Description)} in ({allowedEnumStrings})");
});
Old Way
Alberto Monteiro answered this very well. I had to make a few adjustments to get it to work with EF core.
Rename your enum and add description decorators
public enum FacultyEnum
{
[Description("English Professor")]
Eng,
[Description("Math Professor")]
Math,
[Description("Economics Professor")]
Eco
}
Create a class that represent the table
public class Faculty
{
private Faculty(FacultyEnum #enum)
{
Id = (int)#enum;
Name = #enum.ToString();
Description = #enum.GetEnumDescription();
}
protected Faculty() { } //For EF
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required, MaxLength(100)]
public string Name { get; set; }
[MaxLength(100)]
public string Description { get; set; }
public static implicit operator Faculty(FacultyEnum #enum) => new Faculty(#enum);
public static implicit operator FacultyEnum(Faculty faculty) => (FacultyEnum)faculty.Id;
}
Your model reference the class
public class ExampleClass
{
public virtual Faculty Faculty { get; set; }
}
Create a extension method to get description from enum and seed values
using System;
using System.ComponentModel;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
public static class Extensions
{
public static string GetEnumDescription<TEnum>(this TEnum item)
=> item.GetType()
.GetField(item.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault()?.Description ?? string.Empty;
}
Add the seed in YourDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Faculty>().HasData(FacultyEnum.Eng, FacultyEnum.Math, FacultyEnum.Eco);
}
Add the enum table in your DbContext
public class MyClass : DbContext
{
public DbSet<ExampleClass> Examples { get; set; }
public DbSet<Faculty> Facultys { get; set; }
}
Use it
var example = new ExampleClass();
example.Faculty = FacultyEnum.Eng;
if (example.Faculty == FacultyEnum.Math)
{
//code
}
To remember
If you don't add virtual in Faculty property, you must use Include method from DbSet to do Eager Load
var exampleFromDb = dbContext.Examples.Include(x => x.Faculty).SingleOrDefault(e => e.Id == 1);
if (example.Faculty == FacultyEnum.Math)
{
//code
}
If Faculty property is virtual, then just use it
var exampleFromDb = dbContext.Examples.Find(1);
if (example.Faculty == FacultyEnum.Math)
{
//code
}

Categories