EF Core defaultValueSql how to set value from SELECT - c#

I have DB migration where FormTemplateId - foreign key and I need to set default value as Id of the latest record in [dbo].[FormTemplates]:
migrationBuilder.AddColumn<int>(
name: "FormTemplateId",
table: "ContainerMasterTypes",
type: "int",
nullable: false,
defaultValueSql: "SELECT TOP 1 [Id] FROM [dbo].[FormTemplates] ORDER BY [Id] DESC");
and have error:
Subqueries are not allowed in this context. Only scalar expressions are allowed.'
What's wrong? How can I fix that error?

Entity Framework only allows you to do what you can do already in SQL Server. For example, with the following table setup:
create table dbo.FormTemplates (
Id int not null identity(1,1),
Name nvarchar(50) not null
);
insert dbo.FormTemplates (Name) values (N'Foo'), (N'Bar'), (N'Baz');
create table dbo.ContainerMasterTypes (
Id int not null identity(1,1),
Name nvarchar(50) not null
);
Attempting to modify the ContainerMasterTypes table to add the new column with a subquery in the default constraint as you are doing would fail...
alter table dbo.ContainerMasterTypes
add FormTemplateId int not null
constraint DF_ContainerMasterTypes_FormTemplateId
default (SELECT TOP 1 [Id] FROM [dbo].[FormTemplates] ORDER BY [Id] DESC);
... with the error message that you are seeing in .NET:
Msg 1046 Level 15 State 1 Line 3
Subqueries are not allowed in this context. Only scalar expressions are allowed.
To do this in SQL Server you would instead wrap the query in a Scalar User-Defined Function and reference that from the default constraint, i.e.:
create function dbo.LatestFormTemplateId()
returns int as
begin
return (SELECT TOP 1 [Id] FROM [dbo].[FormTemplates] ORDER BY [Id] DESC)
end
go
alter table dbo.ContainerMasterTypes
add FormTemplateId int not null
constraint DF_ContainerMasterTypes_FormTemplateId
default (dbo.LatestFormTemplateId());
Testing that with data...
insert dbo.ContainerMasterTypes (Name, FormTemplateId) values (N'Something', 1);
insert dbo.ContainerMasterTypes (Name) values (N'Else');
select * from dbo.FormTemplates;
select * from dbo.ContainerMasterTypes;
Would yield the results:
Id
Name
1
Foo
2
Bar
3
Baz
Id
Name
FormTemplateId
1
Something
1
2
Else
3
You need to do the same thing from Entity Framework as well, creating a Scalar UDF that wraps your query and then adding your new column with the default constraint referencing the UDF.

Related

INSTEAD OF SQL Server trigger using C# code that can better than update

I have a table and I fill one of the columns with a trigger if it is null or empty. I want to delete the trigger and do its job in code.
Do I have to first insert and after update or is there a better way?
In .NET Framework, ORM is NHibernate
CREATE TABLE [dbo].[Table]
(
[Id] INT NOT NULL PRIMARY KEY,
[Col1] NVARCHAR(50) NOT NULL,
[Col2] NVARCHAR(50) NOT NULL,
[Code] NVARCHAR(100) NULL
);
CREATE TRIGGER Update_Table
ON [dbo].[Table]
AFTER INSERT
AS
BEGIN
DECLARE #id INT
SELECT #id = Id
FROM inserted
UPDATE [dbo].[Table]
SET Code = 'CODE' + Id
FROM [dbo].[Table]
WHERE Id = #id AND Code IS NULL
END
I did this
Table entity = new Table() { Col1 = "aaa", Col2 = "bbb" };
entity = _repo.insert(entity);
entity.Code = "CODE" + entity.Id;
_repo.Update(entity);
sometimes i do not need update. Because users send this column value.
Table entity = new Table() { Col1 = "aaa", Col2 = "bbb", Code = "ccc" };
entity = _repo.insert(entity);
I tried insert then update. It is OK. Just seeking a better way.
I would simplify it by making CODE computed column, like this
CREATE TABLE [dbo].[Table]
(
[Id] INT NOT NULL PRIMARY KEY,
[Col1] NVARCHAR(50) NOT NULL,
[Col2] NVARCHAR(50) NOT NULL,
[Code] AS 'Code' + CAST(Id as NVARCHAR)
)
so, when inserting data, Code will be populated automatically
Notwithstanding Nino's answer, an interceptor is common way to achieve this.
Update:
It appears that event listeners are also an applicable technique too: https://stackoverflow.com/a/867356/1162077
You don't say how you're generating the entity id when it's not supplied by, so the event you intercept/handle will depend on how you're doing that.

Unable to add new user claim due to constraint conflict of primary key

I have a net core web app net core 3.1 using Oracle database. The tables are generated using MS.EntityFrameworkCore.Tools ('add-migration' & 'update-database' commands).
The auto-generated tables specifically, AspNetUserClaims & AspNetRoleClaims both uses Oracle's auto identity generation.
The issue I have is with the given code, I am just not able to add a new user claim due to constraint conflict.
OracleException: ORA-00001: unique constraint (SYSTEM.PK_AspNetUserClaims) violated.
SQL for the table:
CREATE TABLE "SYSTEM"."AspNetUserClaims"
(
"Id" NUMBER(10,0) GENERATED BY DEFAULT AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
"UserId" NVARCHAR2(450) NOT NULL ENABLE,
"ClaimType" NVARCHAR2(2000),
"ClaimValue" VARCHAR2(3000 CHAR),
CONSTRAINT "PK_AspNetUserClaims" PRIMARY KEY ("Id")
......
)
C# code:
IdentityUserClaim<string> userClaim = new IdentityUserClaim<string> { UserId = user.Id, ClaimType = "WT", ClaimValue = JsonConvert.SerializeObject(jsonWebToken) };
//// var last = await _context.UserClaims.LastOrDefaultAsync(); // dumb workaround
//// userClaim.Id = last == null ? 1 : last.Id + 1;
await _context.UserClaims.AddAsync(userClaim);
GENERATED ALWAYS, attempting to insert a value will result in error, but id is an integer with value 0 as default.
GENERATED DEFAULT, generated if no value is provided, same as above, an integer is 0 as default.
GENERATED BY DEFAULT ON NULL, integer is not nullable.
How can I proceed to create a user claim from here on?
Please ignore the mixed case, system schema, quotations or what not.
Generated by default, while providing its value manually:
SQL> create table test(id number generated by default as identity primary key, name varchar2(10));
Table created.
SQL> insert into test (id, name) values (1, 'Little');
1 row created.
SQL> insert into test (id, name) values (1, 'Foot');
insert into test (id, name) values (1, 'Foot')
*
ERROR at line 1:
ORA-00001: unique constraint (DP_4005.SYS_C001685565) violated
If you omit ID:
SQL> insert into test (name) values ('Foot');
insert into test (name) values ('Foot')
*
ERROR at line 1:
ORA-00001: unique constraint (DP_4005.SYS_C001685571) violated
Generated always; ID value provided manually:
SQL> create table test(id number generated always as identity primary key, name varchar2(10));
Table created.
SQL> insert into test (id, name) values (1, 'Little');
insert into test (id, name) values (1, 'Little')
*
ERROR at line 1:
ORA-32795: cannot insert into a generated always identity column
So - don't provide the ID, let Oracle handle it:
SQL> insert into test (name) values ('Little');
1 row created.
SQL> insert into test (name) values ('Foot');
1 row created.
SQL> select * From test;
ID NAME
---------- ----------
1 Little
2 Foot
Conclusion? Let database handle columns which are to be generated; don't provide those values manually.
Apart from that, the first line you posted:
CREATE TABLE "SYSTEM"."AspNetUserClaims"
has 2 "errors" you'd want to avoid:
SYSTEM, as well as SYS, are special users in an Oracle database, they own it and should not be used except by DBAs while maintaining the database. Creating your own objects in any of those schemas is/can be a HUGE mistake. If you do something wrong, you might destroy the database.
double quotes (Caius Jardas already commented): don't use them in Oracle. You can use any letter case you want, but object names will default to uppercase.
See a few examples:
SQL> create table TesT (nAME varCHAr2(10));
Table created.
SQL> select table_name, column_name from user_tab_columns where table_name = 'TEST';
TABLE_NAME COLUMN_NAME
-------------------- --------------------
TEST NAME
SQL> select NamE from TEst;
no rows selected
But, with double quotes, you have to match letter case every time (and, of course, use double quotes every time):
SQL> create table "TesT" ("nAME" varchar2(10));
Table created.
SQL> select table_name, column_name from user_tab_columns where table_name = 'TesT';
TABLE_NAME COLUMN_NAME
-------------------- --------------------
TesT nAME
SQL> select * From test;
select * From test
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> select name from "test";
select name from "test"
*
ERROR at line 1:
ORA-00942: table or view does not exist
SQL> select name from "TesT";
select name from "TesT"
*
ERROR at line 1:
ORA-00904: "NAME": invalid identifier
SQL> select "nAME" from "TesT";
no rows selected
SQL>
So - forget about double quotes while in Oracle.

Procedure not working because of unresolved reference to object

I'm writing a WPF application where at some point I'm trying to add new row to my database through procedure like below:
CREATE PROCEDURE dbo.InsertStudent
#IdStudent INT,
#FirstName VARCHAR(50),
#LastName VARCHAR(50),
#Address VARCHAR(50),
#IndexNumber VARCHAR(50),
#IdStudies INT
AS
SET NOCOUNT ON
INSERT INTO [dbo].[apbd.Student]
([IdStudent]
,[FirstName]
,[LastName]
,[Address]
,[IndexNumber]
,[IdStudies])
VALUES
(#IdStudent
,#FirstName
,#LastName
,#Address
,#IndexNumber
,#IdStudies)
but whenever I'm about to use it, I'm getting error:
SQL71502: Procedure: [dbo].[InsertStudent] has an unresolved reference to object [dbo].[apbd.Student].
I was looking for solution but what I've found was only to add reference to database through right click on References and so on, but I do not have this option in my solution explorer.
Maybe I'm looking for it in wrong places but the only options I have after right click are something like this:
Add reference...
Add reference to service...
Add connected/concatenated/accumulative (or however should it be translated) service
Add analyzer...
Manage NuGet packets...
as for the code behind creation of the tables in database:
CREATE SCHEMA apbd;
GO
-- tables
-- Table: Student
CREATE TABLE apbd.Student (
IdStudent int NOT NULL IDENTITY,
FirstName nvarchar(100) NOT NULL,
LastName nvarchar(100) NOT NULL,
Address nvarchar(100) NOT NULL,
IndexNumber nvarchar(50) NOT NULL,
IdStudies int NOT NULL,
CONSTRAINT Student_pk PRIMARY KEY (IdStudent)
);
-- Table: Student_Subject
CREATE TABLE apbd.Student_Subject (
IdStudentSubject int NOT NULL IDENTITY,
IdStudent int NOT NULL,
IdSubject int NOT NULL,
CreatedAt datetime NOT NULL,
CONSTRAINT Student_Subject_pk PRIMARY KEY (IdStudentSubject,IdStudent,IdSubject)
);
-- Table: Studies
CREATE TABLE apbd.Studies (
IdStudies int NOT NULL IDENTITY,
Name nvarchar(100) NOT NULL,
CONSTRAINT Studies_pk PRIMARY KEY (IdStudies)
);
-- Table: Subject
CREATE TABLE apbd.Subject (
IdSubject int NOT NULL IDENTITY,
Name nvarchar(100) NOT NULL,
CONSTRAINT Subject_pk PRIMARY KEY (IdSubject)
);
-- End of file.
A MS SQL Server database, by default, only has a single schema (dbo). You can add schemas to group things for either security or organizational purposes.
In your case, the schema apbd was created and Student was created on that schema not the dbo schema. So, to reference that table, you need to use [apbd].[Student].
I would run the following to determine the actual name and schema of the table:
SELECT
CAST(
MAX(
CASE
WHEN
TABLE_SCHEMA = 'apbd'
AND TABLE_NAME = 'Student'
THEN 1
ELSE 0
END
) AS bit
) [The table is apbd.Student]
,
CAST(
MAX(
CASE
WHEN
TABLE_SCHEMA = 'dbo'
AND TABLE_NAME = 'apbd.Student'
THEN 1
ELSE 0
END
) AS bit
) [The table is dbo.apbd.Student]
FROM INFORMATION_SCHEMA.TABLES
I'm also wondering if you perhaps need a USE statement at the start of your CREATE script - are you creating the procedure on the right database?
If the table is on a different database you would need to reference the database in your stored procedure, i.e. [DatabaseName].[dbo].[apbd.Student].

Error inserting row to the sql server from linqtosql with the existance of a trigger

I created a trigger for the table below in the sql server,
Buildings: ID decimal(24, 0) PK, Name varchar(255)
The trigger is
CREATE TRIGGER [dbo].[TRG_BLD]
ON [dbo].[Building]
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO Building (Name)
SELECT Name
FROM inserted
END
All it does, only inserting the row into the table (it does more, but I'm trying to simplify my case).
When I'm inserting a row from the sql server, everything is fine,
but when I'm inserting through LinqToSql,
Building b = new Building();
b.Name = "building A";
DC.Buildings.InsertOnSubmit(b);
DC.SubmitChanges();
an exception occurs on 'SubmitChanges' saying :
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Data.Linq.dll
Additional information: The null value cannot be assigned to a member with type System.Decimal which is a non-nullable value type.
IF OBJECT_ID('dbo.Building', 'U') IS NOT NULL
DROP TABLE dbo.Building
GO
CREATE TABLE dbo.Building
(
ID INT IDENTITY(1,1) PRIMARY KEY,
Name VARCHAR(255)
)
GO
CREATE UNIQUE NONCLUSTERED INDEX ix ON dbo.Building (Name) WITH(IGNORE_DUP_KEY=ON)
GO
CREATE TRIGGER dbo.TRG_BLD
ON dbo.Building
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.Building (Name)
SELECT Name
FROM INSERTED
END
GO
INSERT INTO dbo.Building (Name)
VALUES ('a'), ('a'), ('b'), ('c'), (NULL), (NULL)
GO
SELECT *
FROM dbo.Building
output -
ID Name
----------- ------------
1 a
3 b
4 c
5 NULL

How to make a foreign key in T-SQL to null

I have a tableA that have foreign key to tableB:
TableA (id, tableBId, attributeA)
Now I want to insert a row into tableA but set the tableBId to NULL because at that time, I cannot determine it.
Here is my sql query :
insert into TableA values (tableId, attributeA)
tableId I have set to 0 or -1, but always receive an error :
The INSERT statement conflicted with the FOREIGN KEY
I understand this error, because 0 and -1 doesn't belong to any id of TableB. So, my question is : how can I set null to tableBId in this case?
Set the tableBId column to accept nulls
On the C# side, pass DbNull.Value for tableId value, instead of 0 or -1
edit
string sql = "INSERT INTO TableA (tableId, attributeA) values (#tableId, #attributeA)";
conn.Open();
SqlCommand cmd = new SqlCommand(sql, conn);
cmd.Parameters.Add("#tableId", SqlDbType.Int);
cmd.Parameters["#tableId"].Value = tableId > 0 ? tableId : (object)DBNull.Value;
cmd.Parameters.Add("#attributeA", SqlDbType.VarChar);
cmd.Parameters["#attributeA"].Value = tableId;
cmd.ExecuteNonQuery();
You can set the tableId column in the designer to Allow Nulls.
You need to explicitly name the columns in your destination table, like so:
insert into TableA (id, attributeA) values (tableId, attributeA)
your should send the following instruction to your database:
ALTER TABLE TableA ALTER COLUMN tableBId bit null
this will allow null values to be stored in your tableBId field. Default value for newly created records will be Null.
If this script returns an error related to your foreign key, you'll then have to drop the foreign key constraint before running this script. In this case just
drop the constraint:
ALTER TABLE DROP CONSTRAINT myConstraintName
run the script (see before)
reset the constraint:
ALTER TABLE ADD CONSTRAINT myConstraintName foreign key(tableBId) references tableB(id)

Categories