HasColumnType() in ModelBuilder class

Posts   
 
    
sasupport
User
Posts: 3
Joined: 21-Feb-2020
# Posted on: 21-Feb-2020 12:05:39   

Hello,

We were wondering how we could add HasColumnType() logic automatically to the Map methods for all entities. We are struggling with some decimal columns (database-first approach) which are of the SQL type decimal(8, 5), but when running our project it states "Microsoft.EntityFrameworkCore.Model.Validation: Warning: No type was specified for the decimal column ....". It can be solved by just adding HasColumnType("decimal(8, 5)") to the Map method, but obviously this gets overridden every time we generate new C# from the LLBLGen designer.

In the designer, we can see the correct precision and scale are filled in, but it is not reflected in the auto-generated code.

We make use of LLBLGen Pro v5.6.2 and our target is Entity Framework Core v3 using SQL Server.

Thank you.

Frank

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 21-Feb-2020 15:00:05   

We weren't aware EF Core gives errors in this particular case, so we'll add the HasColumnType() directive to the template for decimal types. We'll release an update (hotfix build 5.6.3) on Monday with this change.

In general we don't generate the types out as they're already present in the DDL SQL scripts but in this case it gives a runtime warning which isn't ideal.

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 11:03:42   

This warning doesn't pop up when running unit tests it seems (as we can't reproduce it with a unit test). Will try with a command line app.

(edit) also doesn't show up there when we fetch data from this table.... strange.

entity:


namespace EFCore3.EntityClasses
{
    /// <summary>Class which represents the entity 'DecField'.</summary>
    public partial class DecField : CommonEntityBase
    {
        /// <summary>Method called from the constructor</summary>
        partial void OnCreated();

        /// <summary>Initializes a new instance of the <see cref="DecField"/> class.</summary>
        public DecField() : base()
        {
            OnCreated();
        }

        /// <summary>Gets or sets the DecField1 field. </summary>
        public System.Decimal DecField1 { get; set; }
        /// <summary>Gets or sets the DecField2 field. </summary>
        public System.Decimal DecField2 { get; set; }
        /// <summary>Gets or sets the DecField3 field. </summary>
        public System.Decimal DecField3 { get; set; }
        /// <summary>Gets or sets the DecField4 field. </summary>
        public System.Decimal DecField4 { get; set; }
        /// <summary>Gets or sets the Id field. </summary>
        public System.Int32 Id { get; set; }
    }
}

mapping:


/// <summary>Defines the mapping information for the entity 'DecField'</summary>
/// <param name="config">The configuration to modify.</param>
protected virtual void MapDecField(EntityTypeBuilder<DecField> config)
{
    config.ToTable("DecFields");
    config.HasKey(t => t.Id);
    config.Property(t => t.Id).HasColumnName("ID");
    config.Property(t => t.DecField1);
    config.Property(t => t.DecField2);
    config.Property(t => t.DecField3);
    config.Property(t => t.DecField4);
}

Table:


CREATE TABLE [dbo].[DecFields](
    [ID] [int] NOT NULL,
    [DecField1] [decimal](18, 0) NOT NULL,
    [DecField2] [decimal](18, 5) NOT NULL,
    [DecField3] [decimal](12, 5) NOT NULL,
    [DecField4] [decimal](8, 5) NOT NULL,
 CONSTRAINT [PK_DecFields] PRIMARY KEY CLUSTERED 
(
    [ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

So a simple test table. I think you execute additional code to create the database at startup, correct? (as in: create a migration, or create the db).

Frans Bouma | Lead developer LLBLGen Pro
sasupport
User
Posts: 3
Joined: 21-Feb-2020
# Posted on: 24-Feb-2020 12:56:49   

Hello,

This is the code we are using, I left out some properties to make it more readable.

This is our entity:


namespace Domain.EntityClasses
{
    /// <summary>Class which represents the entity 'dbo.PrePaidTransaction'.</summary>
    public partial class PrePaidTransaction : CommonEntityBase
    {
        /// <summary>Method called from the constructor</summary>
        partial void OnCreated();

        /// <summary>Initializes a new instance of the <see cref="PrePaidTransaction"/> class.</summary>
        public PrePaidTransaction() : base()
        {
            OnCreated();
        }

        /// <summary>Gets or sets the BalanceNew field. </summary>
        public System.Decimal BalanceNew { get; set; }
        /// <summary>Gets or sets the BalanceOld field. </summary>
        public System.Decimal BalanceOld { get; set; }
        /// <summary>Gets or sets the Credits field. </summary>
        public System.Decimal Credits { get; set; }
        /// <summary>Gets or sets the Description field. </summary>
    }
}

Mapping:


protected virtual void MapPrePaidTransaction(EntityTypeBuilder<PrePaidTransaction> config)
{
    config.ToTable("PrePaidTransaction");
    config.Property(t => t.Credits);
    config.Property(t => t.BalanceOld);
    config.Property(t => t.BalanceNew);
}

Table:


CREATE TABLE [dbo].[PrePaidTransaction](
    [Credits] [decimal](18, 3) NOT NULL,
    [BalanceOld] [decimal](18, 3) NOT NULL,
    [BalanceNew] [decimal](18, 3) NOT NULL
)

Also, the context itself was not changed, just the auto-generated code.

Our injection (ConfigureServices in Startup.cs)


services.AddDbContext<OurContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("ConnectionString")));
services.AddScoped<DbContext, OurContext>();

In a unit test environment, I am also not really able to reproduce this, but when we deploy to Azure, we see these exceptions popping up in Sentry (warnings). Also when running the API locally, we can see these warnings in the DEBUG console.

More details: - Web API project using .NET Core 3.1 - Persistence and Model projects using .NET Standard 2.1

Frank

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 13:38:15   

I managed to grab the 'warn:' messages when I added an ILoggerFactory instance to the optionsBuilder:

private readonly ILoggerFactory Logger = LoggerFactory.Create(c => c.AddConsole());

... optionsBuilder .UseLoggerFactory(Logger) .UseSqlServer((....

I had used previously (in tests)

var serviceProvider = toReturn.GetInfrastructure<IServiceProvider>();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddProvider(new MyLoggerProvider());

where toReturn is the context, and MyLoggerProvider isn't logging all the info! simple_smile So that's why I didnt' see it in a unittest environment.

Will now check what it has to yell about and will add code to remove these warnings (if possible).

(edit) seems to be only decimal, so that's good simple_smile We'll get this sorted asap

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 14:49:44   

Hmm, it's pretty buggy on the EF Core side. Or at least I have no idea what they're trying to do. The problem is with fields which have as type 'money'. This type doesn't have any precision/scale. It however maps to a decimal type on the .net side and results in warning 30000. Specifying .HasColumnType("money") still gives the same warning.

We can't emit a precision/scale with it of course (as that's an invalid type) but if we do (which thus results in money(18,2) it no longer gives the error. As this type is also used for generating a schema, we can't simply emit this.

A 'workaround' is assuming 'money' is a synonym for decimal(18,2), but that gives the same problem: if the user wants a 'money' typed field, they don't want to have a decimal(18,2) field.

I'll file an issue on github.

(edit): https://github.com/dotnet/efcore/issues/20043

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 15:34:25   

Ugh... I made a mistake apparently flushed it does work with 'money' simple_smile

Wrapping things up and will then release a hotfix build which fixes this issue

Frans Bouma | Lead developer LLBLGen Pro
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 24-Feb-2020 16:11:22   

All fixed. Please see the 5.6.3 hotfix build in the 5.6 download area under 'My Account' to download the updated designer with the latest templates which should give you the proper HasColumnType() output in the model builder class.

Frans Bouma | Lead developer LLBLGen Pro
sasupport
User
Posts: 3
Joined: 21-Feb-2020
# Posted on: 24-Feb-2020 17:17:30   

Thank you very much for the quick help and solution, I have just rebuild our LLBLGen code project and it now works as expected! simple_smile

Frank