EF Core 2.0 Enums stored as string [duplicate] - entity-framework

This question already has answers here:
Does EF7 (EFCore) support enums?
(2 answers)
Closed 7 months ago.
I was able to store an enum as a string in the database.
builder.Entity<Company>(eb =>
{
eb.Property(b => b.Stage).HasColumnType("varchar(20)");
});
But when it comes time to query EF doesn't know to parse the string into an enum. How can I query like so:
context
.Company
.Where(x => x.Stage == stage)
This is the exception: Conversion failed when converting the varchar value 'Opportunity' to data type int

Value Conversions feature is new in EF Core 2.1.
Value converters allow property values to be converted when reading
from or writing to the database. This conversion can be from one value
to another of the same type (for example, encrypting strings) or from
a value of one type to a value of another type (for example,
converting enum values to and from strings in the database.)
public class Rider
{
public int Id { get; set; }
public EquineBeast Mount { get; set; }
}
public enum EquineBeast
{
Donkey,
Mule,
Horse,
Unicorn
}
You can use own conversion
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(
v => v.ToString(),
v => (EquineBeast)Enum.Parse(typeof(EquineBeast), v));
}
or Built-in converter
var converter = new EnumToStringConverter<EquineBeast>();
modelBuilder
.Entity<Rider>()
.Property(e => e.Mount)
.HasConversion(converter);

You can use this to convert all the Enum of all the properties of all the entities into a string and vice versa :
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using System;
namespace MyApp
{
public class DatabaseContext : DbContext
{
public DbSet<UserContext> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Configure your model here
}
protected override void OnModelCreating(ModelBuilder model)
{
foreach (var entityType in model.Model.GetEntityTypes())
{
foreach (var property in entityType.GetProperties())
{
if (property.ClrType.BaseType == typeof(Enum))
{
var type = typeof(EnumToStringConverter<>).MakeGenericType(property.ClrType);
var converter = Activator.CreateInstance(type, new ConverterMappingHints()) as ValueConverter;
property.SetValueConverter(converter);
}
}
}
}
}
}

You can do this much more easily with a one-liner attribute.
[Column(TypeName = "nvarchar(24)")]
public EquineBeast Mount { get; set; }
That's all you need to do! This is a string to enum value conversion by explicitly specifying the database column type as an attribute on the property. (Documentation)
I had gotten the same error as the OP because I was originally using the [MaxLength] attribute instead.
(The original question is for EF 2.0, and this feature starts in EF 2.1 as others mention in their answers. But google lead me here so I decided to add a useful answer.)

Related

Is there a way to apply value conversion on backing fields?

Question: Is there a way in Entity Framework Core 3.0 to apply value conversion on backing fields?
Context:
Let's say I have a Blog entity that contains a list of string values representing post ids. I want to avoid the need to have a join entity/table (as described here) so I do a conversion to store the list as a string in the DB. I also want to protect my model from being modified directly through the PostIds property, so I want to use a backing field for that (as described here).
Something like this:
public class Blog
{
public int BlogId { get; set; }
private readonly List<string> _postIds;
public IReadOnlyCollection<string> PostIds => _postIds;
public Blog()
{
_posts = new List<Post>();
}
}
And configuration of the context would look something like that:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configuring the backing field
modelBuilder.Entity<Blog>()
.Metadata
.FindNavigation(nameof(Blog.PostIds))
.SetPropertyAccessMode(PropertyAccessMode.Field);
// Trying to configure the value conversion, but that doesn't work...
modelBuilder.Entity<Blog>()
.Property(e => e.PostIds)
.HasConversion(v => string.Join(',', v),
v => v.Split(','));
}
Any ideas how this configuration could be achieved with the current version of Entity Framework (3.0)?

How to specify Unique Key in EF 7 Code First with Data Annotations

You can specified a Unique Key with Fluent Api:
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasIndex(u => u.Nickname)
.IsUnique();
}
}
public class User
{
public int UserId { get; set; }
public string Nickname { get; set; }
}
But can you do it with Data Annotations?
Edit
Methods change in EF7 Beta 8:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.Index(u => u.Nickname)
.Unique();
}
I'm afraid create an Index using Data Annotation is not still supported in EF 7. Check this link.
I also tried to find some info related with that subject in the last releases and I couldn't find anything.
EF 7 beta 8 release notes
EF 7 RC1 release notes
I found now a post from one of the EF developers (divega) saying this:
In EF7 we support defining indexes using the fluent API but not an
attribute, at least no yet. The IndexAttribute you are possibly
referring to is something we added to the EF 6.x package at some point
but never really became a standard DataAnnotation.
We don't want to copy the original attribute from EF6 as is because
there are a few things in it that we would like to change. Also,
having it in DataAnnotations directly would likely make more sense
than adding it to the EF7 package.
I should mention though that it is highly unlikely that we will add
IndexAttribute in the EF7 RTM timeframe.
Update 1
Apparently this is a feature that will not be added to EF Core, at least for now.
From EF Core documentation:
Indexes can not be configured using Data Annotations.
But you can do it using Fluent Api:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasIndex(b => b.Url)
.HasName("Index_Url");
}
In the absence of built in support, you can use a custom attribute of your own to annotate model properties and apply in OnModelCreating():
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var entity in modelBuilder.Model.GetEntityTypes())
{
foreach (var prop in entity.GetProperties())
{
var index = prop.PropertyInfo.GetCustomAttribute<IndexAttribute>();
if (index != null)
{
entity.AddIndex(prop);
}
}
}
}
With a simple marker attribute class:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class IndexAttribute : Attribute
{
}
Then in your model class, just add the attribute to create a secondary index:
public class User
{
public int UserId { get; set; }
[Index]
public string Nickname { get; set; }
}

Change name of Identity Column for all Entities

I am in the process of creating a domain model and would like to have a "BaseEntity" class with an "Id" property (and some other audit tracking stuff). The Id property is the primary key and each Entity in my Domain Model will inherit from the BaseEntity class. Pretty straightforward stuff.....
public class BaseEntity
{
[Key]
public int Id { get; set; }
public DateTime LastUpdate { get; set; }
public string LastUpdateBy { get; set; }
}
public class Location : BaseEntity
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
Using the example above, I would like to map the "Id" field to a "LocationId" column. I understand that I can use the modelBuilder to do this for each entity explicitly by doing something like this:
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
But I would like to do this for every Entity in my domain model and it would be ugly.
I tried the following bit of reflection but did not have any luck. For whatever reason, the compiler "cannot resolve symbol type":
foreach (var type in GetTypesInNamespace(Assembly.Load("Domain.Model"),"Domain.Model"))
{
modelBuilder.Entity<type>().Property(x=>x.Id).....
}
Is there a way to define a convention to override the default PrimaryKey convention to map my "Id" property to a "ClassNameId" property in the database? I am using Entity Framework 6.
You should take a look at Custom Code First Conventions. You need EF6 for it to work, but it looks like you're already using it.
Just to give you an overview, take a look at the following convention I've used to convert PascalCase names to underscore names. It includes a convention for id properties... It also includes an optional table name prefix.
public class UnderscoreNamingConvention : IConfigurationConvention<PropertyInfo, PrimitivePropertyConfiguration>,
IConfigurationConvention<Type, ModelConfiguration>
{
public UnderscoreNamingConvention()
{
IdFieldName = "Id";
}
public string TableNamePrefix { get; set; }
public string IdFieldName { get; set; }
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
var columnName = propertyInfo.Name;
if (propertyInfo.Name == IdFieldName)
columnName = propertyInfo.ReflectedType.Name + IdFieldName;
configuration().ColumnName = ToUnderscore(columnName);
}
public void Apply(Type type, Func<ModelConfiguration> configuration)
{
var entityTypeConfiguration = configuration().Entity(type);
if (entityTypeConfiguration.IsTableNameConfigured) return;
var tableName = ToUnderscore(type.Name);
if (!string.IsNullOrEmpty(TableNamePrefix))
{
tableName = string.Format("{0}_{1}", TableNamePrefix, tableName);
}
entityTypeConfiguration.ToTable(tableName);
}
public static string ToUnderscore(string value)
{
return Regex.Replace(value, "(\\B[A-Z])", "_$1").ToLowerInvariant();
}
}
You use it like this
modelBuilder.Conventions.Add(new UnderscoreNamingConvention { TableNamePrefix = "app" });
EDIT: In your case, the Apply method should be something like this:
public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
if (propertyInfo.Name == "Id")
{
configuration().ColumnName = propertyInfo.ReflectedType.Name + "Id";
}
}
Try this out in your DbContext class;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name.Equals("Id"))
.Configure(c => c.HasColumnName(c.ClrPropertyInfo.ReflectedType.Name + "Id"));
}
int is the CLR Type of my Primary Key fields. I want to refer to all keys in code as Id but DBA's require keys to be Id with Table entity name prefix. Above gives me exactly what I want in my created database.
Entity Framework 6.x is required.
In Entity Framework 6 Code First:
modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
and update-database...
Change in model
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }
to:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long role_id { get; set; }
Then remove this:
//modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
A start to the Dynamic approach if NOT using custom conventions
modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");
You can do this using reflection on the context. Pseudo Code as explanation:
Reflect Context to get a list of POCO names
For each POCO in a dbcontext.
Map Property Id -> string PocoName+Id
Here are the extensions I use for this type of solution.
// DBSet Types is the Generic Types POCO name used for a DBSet
public static List<string> GetModelTypes(this DbContext context) {
var propList = context.GetType().GetProperties();
return GetDbSetTypes(propList);
}
// DBSet Types POCO types as IEnumerable List
public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
.Name.StartsWith("DbSet"))
.Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
}
private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.PropertyType.GenericTypeArguments[0].Name)
.ToList();
return modelTypeNames;
}
private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
.Select(p => p.Name)
.ToList();
return modelNames;
}
However, you will still need to employee dynamic lambda to finish.
Continue that topic here: Dynamic lambda example with EF scenario
EDIT:
Add link to another question that address the common BAse Config class approach
Abstract domain model base class when using EntityTypeConfiguration<T>
Piggybacking on #Monty0018 's answer but this just need to be updated a little if, like me, you're using Entity Framework 7 and/or SQLite.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
try
{
_builder = modelBuilder;
var typeName = typeof(T).Name;
_builder
.Entity(typeof(T))
.Property<int>("Id")
.ForSqliteHasColumnName(typeName + "Id");
}
catch (Exception e)
{
throw e;
}
}

In Entity Framework is there a cleaner way of converting an object type to a string representation for storage?

Very minor thing really but it bugs me slightly so I thought I'd ask. I have the POCO entity Setting and I'm using a code first approach to Entity Framework.
public class Setting
{
[Required]
[MaxLength(128)]
public string Name { get; set; }
[Required]
public Type Type { get; set; }
// Added to support the storing of Type in the database via Entity Framework.
// Really would be nice to find a cleaner way but this isn't actually so bad.
public string TypeString
{
get { return Type.ToString(); }
set { Type = Type.GetType(value); }
}
public string Value { get; set; }
}
As you can see for use in code I'd like to actually be using the Type object but to store this I have ended up adding a TypeString property. Via the DbModelBuilder I then hide the Type property.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<Setting>()
.HasKey(e => e.Name)
.Property(e => e.Name)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder
.Entity<Setting>()
.Ignore(e => e.Type);
modelBuilder
.Entity<Setting>()
.Property(e => e.TypeString)
.HasColumnName("Type");
base.OnModelCreating(modelBuilder);
}
I just was wondering if there was a way of defining a custom property mapping instead of having to add that extra property to my entity.
UPDATE
My reasoning behind these was actually that I just wanted a quick and easy way for developers to be able to configure a few simple settings by logging in, and it was late and this seemed like a quick solution to allow for several settings of various types.
I suppose if if I wanted some strongly typed settings I'd probably look at a generic implementation of setting such as below:
public class Setting<T>
{
[Required]
[MaxLength(128)]
public string Name { get; set; }
public T Value { get; set; }
}
Though I don't believe that is something that will play nice with Entity Framework.
In part though I'm also curious as for some applications I have multiple clients or stakeholders who can each request slightly different validation rules. As such we usually implement and interface and create an implementation per clients or collections of clients. In order that we can more easily add clients and customise their rules we store which implementation of the interface to create for each client. So persisting type information has proved extremely useful in those cases.
Also it's nice to just explore and understand ways that I can quite happily develop an application whilst reducing the need to think how am I going to persist this, or is this going to play nice with Entity Framework as much as possible.
I'm not aware of any way to persist Type directly, but this may feel a bit better:
public class Settings
{
public Type Type
{
get { return Type.GetType(_TypeString); }
set { _TypeString = value.ToString(); }
}
// Backing Field
protected virtual string _TypeString { get; set; }
}
Then you just need to map the protected _TypeString property (solution from here):
public static StringPropertyConfiguration Property<T>(this EntityTypeConfiguration<T> mapper, String propertyName) where T : class
{
Type type = typeof(T);
ParameterExpression arg = Expression.Parameter(type, "x");
Expression expr = arg;
PropertyInfo pi = type.GetProperty(propertyName,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
expr = Expression.Property(expr, pi);
LambdaExpression lambda = Expression.Lambda(expr, arg);
Expression<Func<T, String>> expression = (Expression<Func<T, string>>)lambda;
return mapper.Property(expression);
}
Then, in your ModelBuilder:
modelBuilder
.Entity<Setting>()
.Property("_TypeString")
.HasColumnName("Type");

EF Code First Readonly column

I am using EF Code first with database first approach.
"with Database.SetInitializer(null);"
My table has two columns createddate and amendddate. They are managed by SQL Server using triggers. The idea is that when data entry happens then these columns gets data via triggers.
Now What I want to do is to make this read only from EF Code first point of view. I.e. I want to be able to see the createddate and ameneded dates from my app but I dont want to amend these data.
I have tried using private modifiers on setter but no luck.When I try to add new data to the table it tried to enter DateTime.Max date to the database which throws error from SQL server.
Any idea?
You cannot use private modifiers because EF itself needs to set your properties when it is loading your entity and Code First can only do this when a property has public setter (in contrast to EDMX where private setters are possible (1), (2)).
What you need to do is mark your for CreatedDate with DatabaseGeneratedOption.Identity and your AmendDate with DatabaseGeneratedOption.Computed. That will allow EF to correctly load data from the database, reload data after insert or update so that entity is up to date in your application and at the same time it will not allow you to change the value in the application because the value set in the application will never be passed to the database. From an object oriented perspective it is not a very nice solution but from the functionality perspective it is exactly what you want.
You can do it either with data annotations:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedDate { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime AmendDate { get; set; }
Or with fluent API in OnModelCreating override in your derived context:
modelBuilder.Entity<YourEntity>()
.Property(e => e.CreatedDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<YourEntity>()
.Property(e => e.AmendDate)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
EF core 1.1 or later versions yes you can use read only property in poco classes. What you need to do is using backing-field.
public class Blog
{
private string _validatedUrl;
public int BlogId { get; set; }
public string Url
{
get { return _validatedUrl; }
}
public void SetUrl(string url)
{
using (var client = new HttpClient())
{
var response = client.GetAsync(url).Result;
response.EnsureSuccessStatusCode();
}
_validatedUrl = url;
}
}
class MyContext : DbContext
{
public DbSet Blogs { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasField("_validatedUrl");
}
}
and fluent api...
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.HasField("_validatedUrl")
.UsePropertyAccessMode(PropertyAccessMode.Field);
Take a look here..