Currently I'm working on a little project with .NET Core and PostgreSQL. For my data acccess, I would like to use Dapper.Contrib. Right now, I'm facing the following problem:
The following snippet shows the CREATE_DATABASE.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE TABLE posts (
id UUID DEFAULT uuid_generate_v4 (),
created BIGINT,
value VARCHAR NOT NULL,
PRIMARY KEY (Id)
);
And the following is my model class in .NET Core
[Table("posts")]
public class Post
{
[Column("id")]
public Guid Id { get; set; }
[Column("created")]
public long Created { get; set; }
[Column("value")]
public string Value { get; set; }
}
The insert method looks like this (it is in the base class. In this case T is type of Post):
public void Insert(T entity)
{
_connection.Insert(entity);
}
When trying to insert an Post object, I'm running into following error:
Ein Ausnahmefehler des Typs "Npgsql.PostgresException" ist in System.Private.CoreLib.dll aufgetreten.: '42703: column "Created" of relation "posts" does not exist
When set NpgsqlLogManager.Provider to new ConsoleLoggingProvider(NpgsqlLogLevel.Trace, true, true); there is the following INSERT statement logged:
DEBUG [14313] Executing statement(s):
insert into posts ("Created", "Value") values ($1, $2) RETURNING Id
So, where is the problem here? I thought Dapper.Contrib is doing fine with attributes, but obviously it isn't.
Does anybody know how to fix this issue, except of rename the model properties into lower case?
Thank you so far.
With Dapper.Contrib, there are ways to map table name but apparently, there is no way to map the column name. This feature was planned for 2.x version. Version 2.x is released; but the issue is still open. Looking at the release notes, feature is not yet added.
Please refer to this answer to know about other query generators for Dapper those support column name mapping.
It seems that Dapper.Contrib does not allow to specify a Column attribute (but it does allow to specify a Table attribute). At ticket is open for this case.
But it does support custom column to property mappers.
See this post for more details on how to implement this mapping.
Related
Background:
We're doing an annoying merge due to a poor branching strategy. Note: all code is pseudocode
We updated an entity class to change the Submittal table. We removed the specific report Id and it's foreign key and replaced it with a generic 'ReportId' with no foreign key.
public class Submittal{
int Id {get;set}
string Status {get;set;}
int SpecificReportId {get;set}
}
There were some migrations that were applied that changed the db (db has the updated column and no FK)
The db context has no reference to the old column. The entity now looks like:
public class Submittal{
int Id {get;set}
string Status {get;set;}
int ReportId {get;set}
}
The modelBuilder in the context has no reference to the old column name.
The model snapshot with the migrations has no reference to the old column name.
The repository method for adding the submittal:
public async Task AddSubmittal(Submittal submittal){
using var context = new ReportContext(dbContextOptions);
context.Add(model);
context.SaveChangesAsync();
}
When debugging, the submittal object has ReportId and no SpecificReportId, but when it tries to run SaveChangesAsync() it fails with an exception of "SqlException: Invalid column name 'SpecificReportId'.".
The dbOptions connection string is pointing at the database I expect, and when I ran sql profiler I got the following sql for my query:
INSERT INTO [Schema].[Submittal] ([Status], [SpecificReportId], [ReportId])
VALUES (#p0, #p1, #p2);
SELECT [Id]
FROM [Schema].[Submittal]
WHERE ##ROWCOUNT = 1 AND [Id] = scope_identity();
What the heck is going on? It feels like I must have missed some piece of how the internal model is generated. I have searched the code for the original id, but did not see anything that looked related.
Notice that the query is trying to insert both ReportId and SpecificReportId. That means you have some lost (or inherited) public SpecificReport SpecificReport { get; set; } inside Submittal class (or opposite reference in SpecificReport class), and EF creates shadow foreign key for it. Full dump of what EF knows about your model is available in DbContextModelSnapshot.cs file, updated with each migration (try adding new migration and make sure it's generated as empty, before you rely on it).
Your pseudocode should work as posted, there must be something else in your project.
PS: you can also set Microsoft.EntityFrameworkCore.Database.Command log level to information, as another debugging tool.
A friend reported a problem with a computed column, Entity Framework, and Breeze
We have a table with a "FullName" column computed by the database. When creating a new Person, Breeze sends the FullName property value to the server, even though it’s not being set at all, and that triggers an error when trying to insert the new Person instance. The database throws this exception:
The column "FullName" cannot be modified because it is either a computed column or is the result of a UNION operator.
Here is the relevant portion of the SQL Table definition:
CREATE TABLE [dbo].[Person](
[ID] [bigint] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](100) NULL,
[MiddleName] [varchar](100) NULL,
[LastName] [varchar](100) NOT NULL,
[FullName] AS ((([Patient].[LastName]+',') + isnull(' '+[Patient].[FirstName],'')) + isnull(' '+[Patient].[MiddleName],'')),
...
My friend tells me the corresponding "Code First" class looks something like this:
public class Person {
public int ID {get; set;}
public string FirstName {get; set;}
public string MiddleName {get; set;}
public string LastName {get; set;}
public string FullName {get; set;}
...
}
The answer to this question explains the problem and offers a solution.
Design issues
Everyone looking at this wonders why there is a computed column for FullName and, secondarily, why this property is exposed to the client.
Let's just assume there is a good reason for the computed column, a good reason for the model to get the value from the table instead of calculating the value itself, and a good reason to send it to the client rather than have the client calculate it. Here's what he told me about that;
"We need to include the FullName in queries"
Life works out this way sometimes.
Consequences
Notice that the FullName property has a public setter. The EF metadata generator for the Person class cannot tell that this is a read-only property. FullName looks just like LastName. The metadata say "this is normal read/write property."
Breeze doesn't see a difference either. The client app may not touch this property, but Breeze has to send a value for it when creating a new Person. Back on the server, the Breeze EFContextProvider thinks it should pass that value along when creating the EF entity. The stage is set for disaster.
What can you do if (a) you can't change the table and (b) you can't change the model's FullName property definition?
A Solution
EF needs your help. You should tell EF that this is actually a database computed property. You could use the EF fluent interface or use the attribute as shown here:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public String FullName { get; set; }
Add this attribute and EF knows this property is read-only. It will generate the appropriate metadata and you can save a new Person cleanly. Omit it and you'll get the exception.
Note that this is only necessary for Code First. If he'd generated the model Database First, EF knows that the column is computed and doesn’t try to set it.
Be aware of a similar issue with store-generated keys. The default for an integer key is "store-generated" but the default for a Guid key is "client generated". If, in your table, the database actually sets the Guid, you must mark the ID property with [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
The answer in this thread is not working for me.
I put this annotation on the PK in my customer class like so.
public class Customer
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
The database migration creates a configuration showing this one line for the newly added data annotation:
AlterColumn("dbo.Customers", "Id", c => c.Int(nullable: false));
I run the migration and look at the table in SQL Server Management Studio. The customer Id column still has Identity Specification Yes and Is Identity Yes. What am I doing wrong?
Thanks.
You are not doing anything wrong. That is a limitation (or maybe it can be considered as a bug) of migrations because SQL server doesn't allow changing IDENTITY on existing columns. It can be set only on new columns. If you change identity value in management studio it will internally execute some complex SQL batch involving temporary table and a lot of data manipulation.
The simplest solution is to delete your database and remove identity definition from your initial migration. Otherwise use SQL profiler to see what SQL management studio is doing when changing column's identity value and use that SQL in your migration instead of AlterColumn.
The EF treats the Id field as a PK, and a PK must have unique values. That what Identity Specification means. Table Column Properties
Setting DatabaseGeneratedOption.None only specifies that the Id will not be generated by the DB Engine, and you will have to provided it (which I don't think is a good idea).
I have the following code in my context, and no explicit table-class mapping, yet my database keeps getting created (by my DropCreateDatabaseIfModelChanges initializer) with an EmployeeStatus table, not EmployeeStatuses. Is there a known issue with this, or am I going insane or what?
public DbSet<Department> Departments { get; set; }
public DbSet<EmployeeStatus> EmployeeStatuses { get; set; }
All my other tables are named exactly after their DbSet names, as I expect.
Entity Framework uses its pluralization service to infer database table names based on
the class names in the model—Destination becomes Destinations, Person becomes
People, etc. By convention, Code First will do its best to pluralize the class name and use the results as the name of the table. However, it might not be the same as your
table naming conventions.
You can use the Table Data Annotation to ensure that Code First maps your class to
the correct table name.
I am using EF 4.1 with database first.
Example table:
CREATE TABLE dbo.Foo(
[ID] [int] IDENTITY(1,1) NOT NULL,
Created datetime not null default(getdate()),
Title varchar(80) not null
PRIMARY KEY CLUSTERED ([ID] ASC)
)
EF correctly loads the model with all 3 columns as nullable = false.
Output from code generation item "ADO.NET DbContext Generator":
public partial class Foo
{
public int ID { get; set; }
public System.DateTime Created { get; set; }
public string Title { get; set; }
}
In MVC3 I generate the FooController via the db context and foo model. When I bring up /Foo/Create and hit "Create" on the blank form it shows a validation error on "Created" field but not on "Title".
If I enter only a "created" date I get an exception:
Validation failed for one or more entities. See 'EntityValidationErrors'
property for more details
The exception is "The Title field is required".
I'm not sure why it works fine for one column but not the other. My first fix was to simply add the annotation, however the class code is auto generated by EF.
The only fix that seems to work is to use a partial metadata class: ASP.NET MVC3 - Data Annotations with EF Database First (ObjectConext, DbContext)
I can add the [Required] tag as desired however this should be unnecessary. Is this a bug in EF or am I just missing something?
This isn't a bug, EF simply doesn't add those attributes. As far as i know, the database-first approach (Entity classes generated by the designer) doesn't even perform the validation. The link you're refering to is a valid solution for your problem. The principle of buddy-classes which contain the actual metadata was introduced due to the fact, that you cannot add attributes to existing properties in a partial class.
The code-first approach has a built-in functionality to validate your annotations, see: Entity Framework 4.1 Validation. Another solution when using database-first would be to create a custom code-generator that applies those attributes T4 Templates and the Entity Framework.