Can I store enums as strings in EF 5? - entity-framework

We have been using EF CF for a while in our solution. Big fans! Up to this point, we've been using a hack to support enums (creating an extra field on the model; ignore the enum durring mapping; and map the extra field to the column in the db that we would have used). Traditionally we have been storing our enums as strings(varchars) in the DB (makes it nice and readable). Now with enum support in EF 5 (Beta 2) it looks like it only supports mapping enums to int columns in the DB....Can we get EF 5 to store our enums as their string representation.
Where "Type" is an enum of type DocumentType
public enum DocumentType
{
POInvoice,
NonPOInvoice,
Any
}
I tried to map it using:
public class WorkflowMap : EntityTypeConfiguration<Model.Workflow.Workflow>
{
public WorkflowMap()
{
ToTable("Workflow", "Workflow");
...
Property(wf => wf.Type).HasColumnType("varchar");
}
}
I thought was going to be the magic bullet but..
That just throws:
Schema specified is not valid. Errors: (571,12) : error 2019: Member
Mapping specified is not valid. The type
'Dodson.Data.DataAccess.EFRepositories.DocumentType[Nullable=False,DefaultValue=]'
of member 'Type' in type
'Dodson.Data.DataAccess.EFRepositories.Workflow' is not compatible
with
'SqlServer.varchar[Nullable=False,DefaultValue=,MaxLength=8000,Unicode=False,FixedLength=False]'
of member 'Type' in type 'CodeFirstDatabaseSchema.Workflow'.
Your thoughts?

This is currently not possible. Enum in EF has same limitations as enums in CLR - they are just named set of integer values. Check this article for confirmation:
The EF enum type definitions live in conceptual layer. Similarly to
CLR enums the EF enums have underlying type which is one of Edm.SByte,
Edm.Byte, Edm.Int16, Edm.Int32 or Edm.Int64 with Edm.Int32 being the
default underlying type if none has been specified.
I posted article and related suggestion about this problem. If you want to see this feature in the future please vote for the suggestion.

I hit this problem a few weeks ago. The best I could come up with is a bit hacky.
I have a Gender enum on the class Person, and I use data annotations to map the string to the database and ignore the enum.
public class Person
{
public int PersonID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Column("Gender")]
public string GenderString
{
get { return Gender.ToString(); }
private set { Gender = value.ParseEnum<Gender>(); }
}
[NotMapped]
public Gender Gender { get; set; }
}
And the extension method to get the correct enum from the string.
public static class StringExtensions
{
public static T ParseEnum<T>(this string value)
{
return (T)Enum.Parse(typeof(T), value, true);
}
}
See this post for full details - http://nodogmablog.bryanhogan.net/2014/11/saving-enums-as-strings-with-entity-framework/

Related

How do you map strings in the database to enums in your model without introducing a second property?

My database has a table like this:
Cats
- CatId INT PK
- Name VARCHAR(100)
- FavoriteToy VARCHAR(100)
And my code looks like this:
Cat.cs
public int CatId { get; set; }
public string Name { get; set; }
public Toy FavoriteToy {get; set; }
StaticVariables.cs
public enum Toy { Box, Ball, StuffedAnimal }
In a normalized database I would use a lookup table in the database to store all the toys and then the Cats table would just store a ToyId. But for this situation it's a lot easier to just store the FavoriteToy as a string even though it will be redundant.
The problem is I don't know how to convert a string in the database to an enum in code without creating a second FavoriteToyString property and having FavoriteToy just be a computed that returns the enum derived from FavoriteToyString.
I've heard this might be possible in the current version of entity framework. Is that true? Can you please show me how to do this?
You may use DTO class and automapper to solve your issue :)
Generally, yes a lookup table reference is a better option since your data can comply with referential integrity. That is, No cat records with toys that your Enum hopefully doesn't contain. (Though your Enum would need to be kept in sync with the Toys table.) You can configure EF to store enumerations as a string using a bit of a trick with the mapping:
public class Cat
{
[Key]
public int CatId { get; set; }
public string Name { get; set; }
[Column("FavoriteToy")]
public string FavoriteToyMapped { get; set; }
[NotMapped]
public Toy FavoriteToy
{
get { return (Toy)Enum.Parse(typeof(Toy), FavoriteToyMapped); }
set { FavoriteToyMapped = value.ToString(); }
}
}
The caveat of this approach is that where you might use Linq to Entity to filter on your cat's favorite toy, you need to reference the FavoriteToyMapped value in the query expression because EF/DB won't know what FavoriteToy is.
I.e.
Cats with a favorite toy of "Yarn"
var catsThatLoveYarn = context.Cats.Where(c => c.FavoriteToyMapped == Toys.Yarn.ToString()).ToList();
// not
var catsThatLoveYarn = context.Cats.Where(c => c.FavoriteToy == Toys.Yarn).ToList();
// Will error because EF doesn't map that property.
Once you are working with instances of entities, that the set of entities has been pulled back from the database, you can further access/refine queries with FavoriteToy. Just be cautious and prepared for the unknown field if you use it too early and EF goes and tries to compose a query.
var threeYearOldCats = context.Cats.Where(c => c.Age == 3).ToList();
var threeYearOldCatsThatLoveYarn = threeYearOldCats.Where(c => c.FavoriteToy == Toys.Yarn).ToList();
This is Ok because the .ToList() in the first query executed the EF-to-SQL, so threeYearOldCats is now a local List<Cat> of cat entities, not an IQueryable<Cat>.

Unable to cast the type 'Claim' to type 'ClaimDetail'. LINQ to Entities only supports casting EDM primitive or enumeration types

I have a problem with an EF6 and LINQ to Entities method inasmuch as I cannot cast from one class, Claim, to a derived class, ClaimDetail. I am certain that this cast would be valid but I don't know fiddle with the syntax to get the cast to work.
The relevant portions of the model look like this:
// Claim.cs
public class Claim
{
public int Id { get; set; }
// ..other properties
}
and
// ClaimDetail.cs
public class ClaimDetail : Claim
{
public string ClaimRef { get; set; }
// ..other properties
}
I have another class, Request, that looks like this:
// Request.cs
public class Request
{
public Claim Claim { get; set; }
// ..other properties
}
These classes form a part of the context, like this:
// Context.cs
public DbSet<ClaimDetail> Claims { get; set; }
public DbSet<Request> Requests { get; set; }
Now, I would like to sort a LINQ query of Requests based on a property of ClaimDetail. I have this:
sorted = intermediate.OrderBy(r => ((ClaimDetail)r.Claim).ClaimRef);
where intermediate is an IQueryable<Request>. However, on trying to materialize this query, I receive the following message:
Unable to cast the type 'Claim' to type 'ClaimDetail'. LINQ to Entities only supports casting EDM primitive or enumeration types.
How can I do this, without resorting to calling ToList() on the intermediate results? That would be far too expensive.

Entity Framework 7 Set decimal precision for model builder

I have been trying to figure out how to set the decimal precision for EF7 (Beta 4) with no luck.
I was expecting to do something like:
modelBuilder.Entity<SomeClass>().Property(p => p.DecimalProperty).Precision(10, 6)
This does not appear to be available, but I was able to find the following class in the repository in GitHub:
https://github.com/aspnet/EntityFramework/blob/7.0.0-beta4/src/EntityFramework.Relational/RelationalDecimalTypeMapping.cs
There are no examples of using the RelationalTypeMapping classes or method signatures with them. Maybe this is just used as part of the mapping api for retrieving information?
Another place I might expect this to be is the following:
modelBuilder.Entity<SomeClass>().Property(p => p.DecimalProperty).ForRelational().ColumnType()
or
modelBuilder.Entity<SomeClass>().Property(p => p.DecimalProperty).ForSqlServer().ColumnType()
These only takes a string, is this functionality just not implemented yet or am I just not looking in the correct place?
Edit: Just realized that string is probably for .ColumnType("decimal(10,6)") type of solution until this is built out further, still wouldn't mind getting some clarification though as I would prefer not to use strings for this
Edit: after clarification from bricelam I ended up creating the following extension to use for now to avoid using the string, and I appreciate the simplicity of their approach:
public static RelationalPropertyBuilder DecimalPrecision(this RelationalPropertyBuilder propertyBuilder, int precision, int scale)
{
return propertyBuilder.ColumnType($"decimal({precision},{scale})");
}
Usage example:
modelBuilder.Entity<SomeClass>().Property(p => p.DecimalProperty).ForRelational().DecimalPrecision(10,6);
Edit: Making modification for RC1
I haven't tested these out yet, but I just threw together the following 2 samples of what this will probably look like with RC1
public static PropertyBuilder DecimalPrecision(this PropertyBuilder propertyBuilder, string precision, string scale)
{
return propertyBuilder.HasColumnType($"decimal({precision},{scale})");
}
public static PropertyBuilder SqlDecimalPrecision(this PropertyBuilder propertyBuilder, string precision, string scale)
{
return propertyBuilder.ForSqlServerHasColumnType($"decimal({precision},{scale})");
}
Since I have not yet tried this I am not sure which would be the correct usage between "HasColumnType" or "ForSqlServerHasColumnType", but hopefully this will point someone in the right direction.
Your workaround is the design we intended. Instead of having a bunch of "facets" you can set on a type like precision, scale, max length, unicode/ansi, fixed/variable length, etc. We decided to keep it simple: If the default type mapping isn't what you want, tell us what type to use. There have been talks of going back on this decision and reintroducing the "facets". If you feel strongly about it, I would encourage you to create a new issue.
Also note that there are a bunch of other bugs in type mapping right now, but they should be fixed by the time we release beta5.
The example shown seems to be outdated as per EF RC1.
Here is how I set precision on a decimal field.
Say I have an entity
public class Review
{
public int ReviewId { get; set; }
public decimal TotalScore { get; set; } //I want a precision field in DB
public DateTime CreatedOn { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
then in my context class, on model creating, I instantiate the mapping (I could do the mapping there, but I like to keep it separated)
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options ) : base(options)
{
}
public DbSet<Review> Reviews { get; set; }
//etc.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Mappings
new ReviewMap(modelBuilder.Entity<Review>());
//etc..
}
}
and then the mapping. Remember to use the namespace where the Model extensions are:
using Microsoft.Data.Entity; //here is where the extensions are
public class ReviewMap
{
public ReviewMap(EntityTypeBuilder<Review> entityBuilder)
{
entityBuilder.HasKey(r => r.ReviewId);
//Using the column type extension
entityBuilder.Property(r => r.TotalScore)
.HasColumnType($"decimal(5,2)")
.IsRequired(true);
//and this has nothing to do with the example but it's interesting
//to show how to use Sql command to automatically fulfil a value
//when adding a new Entity
entityBuilder.Property(r => r.CreatedOn)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("GETUTCDATE()")
.IsRequired(true);
}
}

entityframework Inherited classes in single table can not use timestamp?

I want to create a TimeStamp field in Inherited class like this:
[Table("TABLE_A")]
public class A
{
public int ID {get;set;}
public string Name {get;set;}
}
[Table("TABLE_B")]
public class B : A
{
public string Address {get;set;}
[TimeStamp]
public byte[] RowVersion {get;set;}
}
but failed, how can I do here ?
You will see error
Type 'B' defines new concurrency requirements that are not allowed for
subtypes of base EntitySet types.
That means exactly what error says. Entity Framework do not support concurrency checks in derived types. You will see same error if you'll add simple concurrency check instead of timestamp:
[Table("TABLE_B")]
public class B : A
{
[ConcurrencyCheck]
public string Address { get; set; }
}
If you will move concurrency checking to base class, then it will work, but only on base type. If you need checking to be performed on derived type, I think you should use Stored Procedure for updating entity.

Code first TPC Inheritance with concrete base and derived, concrete types in EF

I'm trying to set up a TPC inheritance using Code First to model incoming and outgoing messages and the records therein.
The base type, SentRecord, is concrete and its derived type, ReceivedRecord, is also concrete and inherits from SentRecord and adds a few extra fields in order to record return codes. Something like this, but with more properties:
public class SentRecord : RecordBase {
public int Id { get; set; }
public string FooField { get; set; }
}
public class ReceivedRecord : SentRecord {
public int ReturnCode { get; set; }
public SentRecord SentRecord { get; set; }
}
The current model is TPH and as a result the tables get a descriminator column to identify the type of object that was persisted. It works, but I'd prefer both objects to be stored in separate tables, without the need of the discriminator column. The table SentRecord would only have the columns Id and FooField and the table ReceivedRecord would have Id, FooField, ReturnCode and an FK to SentRecord.
I currently have the following in my DataContext class:
public class Context : DContext {
public DbSet<SentRecord> SentRecords { get; set; }
public DbSet<ReceivedRecord> ReceivedRecords { get; set; }
}
And I have the following configuration for the ReceivedRecord:
public class ReceivedRecord_Configuration : EntityTypeConfiguration<ReceivedRecord>{
public ReceivedRecord_Configuration() {
this.Map(m => {
m.MapInheritedProperties();
m.ToTable("ReceivedRecords");
});
}
}
And the following for SentRecord:
public class SentRecord_Configuration : EntityTypeConfiguration<SentRecord>{
public SentRecord_Configuration() {
this.Map(m => {
m.MapInheritedProperties(); //In order to map the properties declared in RecordBase
m.ToTable("SentRecords");
});
}
}
But once I run this, I get the following error when EF is trying to initialize my database:
Problem in mapping fragments starting at lines 455, 1284:
An entity from one EntitySet is mapped to a row that is also mapped to an entity from another EntitySet with possibly different key.
Ensure these two mapping fragments do not map two unrelated EntitySets to two overlapping groups of rows.
I'm not sure what to do in order to set this up in the TPC way I described above? Or should I stick with TPH which works?
Thanks in advance!
Okay, I got it up and running. Truth be told, the example I gave was a little bit less complex than the actual classes and inheritance hierarchy I was working with. That hierarchy contained a lot of abstract classes and concrete classes from which other classes inherited.
'Flattening' the hierarchy by getting cutting down on inheritance made it work smooth and without any errors whatsoever. Response messages aren't inherited from the sent messages anymore.
Short version: Don't make complex inheritance trees with mixed concrete and abstract base types when trying to use code-first database models. It'll only make it more complex to persist.