EF Core and PostgreSQL enum two-way name translation - postgresql

I am trying to figure out how to enable two-way name translation between postgres DB and EF Core. I have found a link where it is told how values are translated from EF Core to DB, but nothing about from DB to EF Core. The problem is that when I read from DB I get values in snake case, but I need them in pascal case.

How are you reading your data from the DB?
Providing you are writing from and reading into the C# enum type that corresponds to the postgres enum type, Npgsql takes care of translating the values both ways.
For example:
Define your types:
public enum SomeDbEnum
{
FirstValue,
SecondValue
}
public class SomeDbObject
{
public SomeDbEnum DbEnum { get; set; }
}
Map your enum types
protected override void OnModelCreating(ModelBuilder builder)
=> builder.HasPostgresEnum<SomeDbEnum>();
static MyDbContext()
=> NpgsqlConnection.GlobalTypeMapper.MapEnum<SomeDbEnum>();
Try it out:
var toWriteToDb = new SomeDbObject { DbEnum = SomeDbEnum.SecondValue };
context.SomeDbObject.Add(toWriteToDb);
// value inserted as "second_value"
var readFromDb = context.SomeDbObject.FirstOrDefault(x => x.DbEnum == SomeDbEnum.SecondValue);
Console.WriteLine(readFromDb.DbEnum);
// value output as SecondValue
The same thing applies even if you use something other than the default naming translations, i.e. registered some other INpgsqlNameTranslator (other than the default NpgsqlSnakeCaseNameTranslator) or decorate your enum members with the PgNameAttribute.

Had to research myself, just use the NpgsqlNullNameTranslator. For some reason NpgSql uses NpgsqlSnakeCaseNameTranslator as default, at least for EF Core 7.0
modelBuilder.HasPostgresEnum<MyEnum>(nameTranslator: new NpgsqlNullNameTranslator());
EDIT: in order to save them as string in DB:
modelBuilder.Entity<MyEntity>()
.Property(u => u.EnumProperty)
.HasConversion(new EnumToStringConverter<MyEnum>());

Related

Update to .NET 5 DataAnnotations.Schema don't work anymore. LINQ join multiple dbs / querys failing

In Core 3.0 i could join multiple dbs / schemas.
Here is the class Order in dbEarth :
namespace dgNet.Core.Models.Earth
{
[Table("tbl_Order", Schema ="Earth")]
public class Order : EntityBaseWithTypedId<int>
{
[Key]
[Column("BestID")]
public override int Id { get; set; }
Here is class SerialNumber in dbMars
namespace dgNet.Core.Models.Mars
{
[Table("tbl_serialnumber", Schema = "Mars")]
public class SerialNumber : EntityBaseWithTypedId<int>
{
[Column("serialnumber")]
public int Serialnumber { get; set; }
[Column("jobId")]
public int JobId { get; set; }
[ForeignKey("JobId")]
public Order Order { get; set; }
Data Annotations is equivalent to the code here :
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>().ToTable("tbl_Order", "Earth");
}
So if builded a LINQ Query and included Order(dbEarth) in SerialNumber(dbMars) it worked well.
query => query.Include(serialNumber => serialNumber.Order).FirstOrDefault();
After the update to Core 5.0 SQL- Querys are created incorrectly.
SQL joins Orders on the same db / schema like SerialNumbers.
Using newest NuGet packages .AspCore (5.0.10)
DB = MySQL
Using Pomelo.EntityFrameworkCore.MySql (5.0.2)
Someone has that problem?
MySQL does not support the EF Core concept of schemas.
The EF Core concept of schemas is the same one that SQL Server uses, in which schemas are basically just categories (organization units) that you can use to group multiple tables logically together within the same database.
What MySQL calls schemas are actually databases, and a single DbContext does not support multiple databases in EF Core.
Therefore, we officially removed the very brittle multi-database support in Pomelo 3.2.0.
The official way to deal with this is shown in Implement alternatives to the current behavior to always throw, if a schema has been set for an object #982:
There are currently 3 options to choose from:
// Throw an exception, if a schema is being used. This is the default.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Throw))
// Silently ignore any schema definitions.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Ignore))
// Use the specified translator delegate to translate from an input schema and object name to
// an output object name whenever a schema is being used.
options.UseMySql(myConnectionString, b => b.SchemaBehavior(MySqlSchemaBehavior.Translate,
(schema, entity) => $"{schema ?? "dbo"}_{entity}"))
There is also a way to explicitly enable the old behavior, as illustrated in
method ModelBuilder.HasDefaultSchema is not working (No database selected)
#22971 (comment) for Pomelo 3.2.x:
[...]
In essence, there are two steps:
You need to derive from MySqlSqlGenerationHelper and override GetSchemaName:
public class CustomMySqlSqlGenerationHelper : MySqlSqlGenerationHelper
{
public CustomMySqlSqlGenerationHelper(
RelationalSqlGenerationHelperDependencies dependencies,
IMySqlOptions options)
: base(dependencies, options)
{
}
protected override string GetSchemaName(string name, string schema)
=> schema; // <-- this is the first part that is needed to map schemas to databases
}
You need to provide a schema name translator:
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // use our ServiceProvider
.UseMySql(
"server=127.0.0.1;port=3308;user=root;password=;database=EFCoreIssue22971_01_IceCreamParlor",
b => b.ServerVersion("8.0.21-mysql")
.SchemaBehavior(
MySqlSchemaBehavior.Translate,
(schemaName, objectName) => objectName) // <-- this is the second part that is needed to map
// schemas to databases
.CharSetBehavior(CharSetBehavior.NeverAppend))
.EnableSensitiveDataLogging()
.EnableDetailedErrors();

Code First creates int instead of enum

I want to create a enum type column named 'type' but when I reverse engineer my Code First generated DB it assigns it as an 'int'.
Here is my Enum class:
[Flags]
public enum TypeNames
{
Een = 0,
Twee = 1,
Drie = 2
}
Here is my Grounds class to create the table with the 'TypeNames'-enum. The Properties class is another table (Grounds - Properties have a TPT inheritance).
[Table("gronden")]
public partial class Grounds : Properties
{
[Column("opp")]
public double? Surface { get; set; }
[EnumDataType(typeof(TypeNames)), Column("type")]
public TypeNames Types { get; set; }
}
Any ideas of what I am missing here to get an enum-type into my DB?
According to the following answer, it appears that EnumDataTypeAttribute is only implemented for ASP.NET UI components, and not for EF usage.
Should the EnumDataTypeAttribute work correctly in .NET 4.0 using Entity Framework?
EF Core 2.1 implements a new feature that allows Enums to be stored as strings in the database. This allows data to be self-describing, which can be really helpful for long-term maintainability. For your specific case, you could simply do:
[Table("gronden")]
public partial class Grounds : Properties
{
[Column("opp")]
public double? Surface { get; set; }
[Column("type", TypeName = "nvarchar(24)")]
public TypeNames Types { get; set; }
}
You can find more detail on this page:
https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions
Also, I noticed that you have the FlagsAttribute set on your enum. Are you hoping to be able to apply multiple enum values to a single entity? This should work fine when values are persisted as an int, but will not work if storing as a MySQL ENUM or string datatype. MySQL does support a SET datatype, but it seems unlikely that EF would add support for this feature, since most other databases don't have a similar concept.
https://dev.mysql.com/doc/refman/5.6/en/constraint-enum.html
If you do indeed want to allow multiple enum values to be applied to each entity (similar to the way tags are used on Stack Overflow), you might consider creating a many-to-many relationship instead. Basically, this would mean converting the TypeNames enum into a Types table in the database, and allowing EF to generate a GroundTypes table to link them together. Here is a tutorial:
http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx

Ignore Entities by Default

I have an existing database, to which I'd like to add Entity Framework mappings for just a handful of tables/entities. Is there a way to ignore all entities by default, and then selectively include them?
I have this in the context constructor to not migrate changes:
Database.SetInitializer(new NullDatabaseInitializer<Context>());
And then I have the following fluent code to map the existing entities:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Cube>()
.Map(e => e.ToTable("tblCubes"))
.HasKey(e => e.CubeId);
...
However, when I run any EF queries, I get the error:
One or more validation errors were detected during model generation.
EntityType 'xyz' has no key defined. Define the key for this EntityType
Rather than using modelBuilder.Ignore<xyz>(); on every existing and future entity, can't I just get EF to ignore all by default, and only map those I choose/include?
EDIT============
One of my EF entities (CubeFact) has relational properties to other classes like this one below to the Year class:
private Year _year;
public Int16 YearId { get; set; }
public Year Year { get { return _year ?? (_year = Year.GetYearById(YearId)); } set { _year = value; } }
The Year class then links to a Fact class, which is one of the classes failing validation. But neither the Year class nor the Fact class have been explicitly mapped. Does EF follow these relationships and then validate, even if I haven't explicitly told it about the relationships?

Entity Framework doesn't query derived classes - Error in DbOfTypeExpression

I have a base class and two derived classes.
Each of the derived classes implements the same type as a property - the only difference is the property name.
Sadly I don't have much influence on the class design -> they have been generated from a wsdl file.
I then have a property on the BaseType to encapsulate the common property. The plan was to use this property in my web views etc.
I have used the famous "Fruit-Example" to demonstrate the problem:
public class FruitBase
{
public virtual int ID { get; set; }
//
// The plan is to use this property in mvc view
//
[NotMapped]
public virtual FruitnessFactor Fruitness
{
get
{
if (this.GetType().BaseType == typeof(Apple))
return ((Apple)this).AppleFruitness;
else if (this.GetType().BaseType == typeof(Orange))
return ((Orange)this).OrangeFruitness;
else
return null;
}
}
}
public class FruitnessFactor { }
In my MVC controller, the following query works absolutely fine:
return View(context.FruitEntities
.OfType<Apple>().Include(a =>a.AppleFruitness)
.ToList());
But this one doesn't:
return View(context.FruitEntities
.OfType<Apple>().Include(a =>a.AppleFruitness)
.OfType<Orange>().Include(o => o.OrangeFruitness)
.ToList());
The error message I get is:
DbOfTypeExpression requires an expression argument with a polymorphic result type that is compatible with the type argument.
I am using EF 5.0 RC and the Code First approach.
Any help is much appreciated!
As far as I can tell you can't apply Include on multiple subtypes in a single database query. You can query one type (OfType<Apple>().Include(a => a.AppelFruitness)) and the same for another subtype. The problem is that you can't concat the results in the same query because the result collections have different generic types (apples and oranges).
One option would be to run two queries and copy the result collection into a new collection of the base type - as you already indicated in the comment section under your question.
The other option (which would only need a single query) is a projection. You would have to define a projection type (you could also project into an anonymous type)...
public class FruitViewModel
{
public FruitBase Fruit { get; set; }
public FruitnessFactor Factor { get; set; }
}
...and then can use the query:
List<FruitViewModel> fruitViewModels = context.FruitEntities
.OfType<Apple>()
.Select(a => new FruitViewModel
{
Fruit = a,
Factor = a.AppleFruitness
})
.Concat(context.FruitEntities
.OfType<Orange>()
.Select(o => new FruitViewModel
{
Fruit = o,
Factor = o.OrangeFruitness
}))
.ToList();
If you don't disable change tracking (by using AsNoTracking) the navigation properties get populated automatically when the entities get attached to the context ("Relationship fixup") which means that you can extract the fruits from the viewModel collection...
IEnumerable<FruitBase> fruits = fruitViewModels.Select(fv => fv.Fruit);
...and you'll get the fruits including the FruitnessFactor properties.
This code is pretty awkward but a direct approach without using a projection has been asked for several times without success:
bottleneck using entity framework inheritance
Entity Framework - Eager loading of subclass related objects
How do I deeply eager load an entity with a reference to an instance of a persistent base type (Entity Framework 4)

How do I map a char property using the Entity Framework 4.1 "code only" fluent API?

I have an object that has a char property:
public class Product
{
public char Code
{
get;
set;
}
}
Entity Framework doesn't seem to be able to map chars (this field is missing from the database when I create the database schema from my model objects). Is there anyway I can map the char (e.g. to a string) using the fluent API? I don't want to change the model objects as they are part of a legacy shared library.
Char is not valid primitive type for entity framework = entity framework doesn't map it. If you check CSDL reference you will see list of valid types (char is not among them).
Database char(1) is translated as string (SQL to CSDL translation). Char is described as non-unicode string with fixed length 1.
The only ugly option is second mapped property using string and your char non-mapped property will just use string[0] from that property. That is just another example how some simple type mapping or converters are damn missing in EF.
In Fluent API you can specify the database column data type using the HasColumnType method like this:
modelBuilder.Entity<Product>()
.Property(p => p.Code)
.HasColumnType("char");
According to Andre Artus' answer here, HasColumnType is available in EF4.1.
For those using Data Annotations, the ColumnAttribute can accomplish the same thing.
[Column(TypeName="char")]
public string Code { get; set; }
[Column( TypeName = "char(1)" )]
works for me with EF core 3.1.4
I have tried all the ways I have imagined and I must say the the accepted answer is the unique way to solve the problem of the char type as far as I know.
The char type isn't available for its use in EntityFramework.
Fluent API is included in this restriction.
If you try to put a char on the Property(p => p.MyCharProperty) will give you an Exception.
That means that char properties aren't available for Fluent API nor Attributes.
The easiest solution is this (as proposed by Ladislav Mrnka).
public class Product
{
public char Code { get; set; }
[Column("Code", TypeName="char")]
[MaxLength(1)]
public string CodeString
{
get { return Code.ToString(); }
set { Code = value[0]; }
}
}
One note: you can't put the property private, protected or internal. Must be public.
Fluent API version would be like this.
public class Product
{
public char Code { get; set; }
//We need the property but we will use the Fluent API to replace the attributes
public string CodeString
{
get { return Code.ToString(); }
set { Code = value[0]; }
}
}
modelBuilder.Entity<Product>().Property(p => p.Code)
.HasTypeName("char")
.HasMaxLength(1)
There is alternate ways to tackle this issue for TESTING purpose only. Make the field not null to null able for time being from design mode. Sometime it is restricted SQL Management Studio. (Change setting Tools -> Option ->Designer -> Table Database designer -> Uncheck "Prevent saving changes that required table creation"