Update the record according to the ID of inserted record in LINQ to SQL - asp.net

I want to insert a record and then update the record according to scope_identity of inserted record.
I'm doing this but when I want to update my record encounter an error.
WorkshopDataContext Dac = new WorkshopDataContext();
Dac.Connection.ConnectionString = "Data Source=dpsxxx-xxx;Initial Catalog=kar;User ID=sa;Password=xxxx";
Tbl_workshop Workshop = new Tbl_workshop();
Workshop.StateCode = Bodu.BduStateCode;
Workshop.CityCode = Bodu.BduCityCode;
Workshop.Co_workshop=12222;
Dac.Tbl_workshop.InsertOnSubmit(Workshop);
Dac.SubmitChanges();
Int64 Scope = Workshop.id;
var query = from record in Dac.Tbl_workshop where record.id == Scope select record;
query.First().co_Workshop = Scope;
Dac.SubmitChanges();
and this is the error:
Value of member 'co_Workshop' of an object of type 'Tbl_Workshop' changed.
A member defining the identity of the object cannot be changed.
Consider adding a new object with new identity and deleting the existing one instead.

If you have properly configured your Linq-to-SQL model to reflect the IDENTITY column in your table, you should have the new value available right after .SubmitChanges():
Tbl_workshop Workshop = new Tbl_workshop();
Workshop.StateCode = Bodu.BduStateCode;
Workshop.CityCode = Bodu.BduCityCode;
Workshop.Co_workshop=12222;
Dac.Tbl_workshop.InsertOnSubmit(Workshop);
Dac.SubmitChanges();
Int64 workshopID = Workshop.Id; // you should get new ID value here - automatically!!
You don't need to do anything like reading out that new value from SQL Server or anything - Linq-to-SQL should automagically update your Workshop object with the proper value.
Update: to update your co_workshop value to the value given by the IDENTITY ID, do this (just set the value of co_workshop and save again - that's really all there is):
Dac.Tbl_workshop.InsertOnSubmit(Workshop);
Dac.SubmitChanges();
Int64 workshopID = Workshop.Id; // you should get new ID value here - automatically!!
Workshop.Co_workshop = workshopID;
Dac.SubmitChanges();

As it said on the error, you can't change co_Workshop because its identity (auto increment value). To freely edit it, you need to edit the database and remove this setting.
What probably is happening is that both id and co_Workshop are set as identity. Just disable the identity checkbox from co_Workshop.

Related

Unique serial number generation - Entity Framework ASp.net MVC

I am developing a complaint management in which I have to generate unique serial number for each complaint like 00001/20 {Serial number/year}.
I am using repository pattern and i am generating this complaint number using the following code snippet but problem is if two user try to lodge a complaint at the same time it will generate a same complaint no and that thrown an error as I am keeping a serial number in a separate table which is also mentioned below for reference. Let me know the best way to achieve this
int serialNo = repository.serialNo.Find(c => c.Year == DateTime.Now.Year).FirstOrDefault().TicketCounter;
string complaintNo = string.Format("{0}", serialNo.ToString().PadLeft(5, '0'));
model.Id = repository.complaintRepo.GetMaxPK(c => c.Id);
I am using repository pattern.
I guess, one of the solutions is to setup the table so that it generates required ID automatically on every new row. This ensures that the ID is always unique.
CREATE SEQUENCE MySequence
AS int
START WITH 1
INCREMENT BY 1;
CREATE TABLE Complaint
(
Id char(8) CONSTRAINT [DF_Complaint_ID]
DEFAULT FORMAT((NEXT VALUE FOR MySequence), '0000#')
+'/'+RIGHT(YEAR(GETDATE()),2),
Foo int,
Bar int,
CONSTRAINT [PK_MyTable] PRIMARY KEY (Id)
);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=18a5d0fec80a3985e30cef687d3c8e49
So there will be no need to assign the id manually and your code could look like
var c = repository.Insert(new model
{
Foo = ...
Bar = ...,
...
});
repository.Save();
// you can get id after inserting data in the database
string id = c.Id;

How to set a table field primary in design-time Firedac?

I want to make a not-null non-auto-inc integer my primary key, but I am unable to do so in design time with Firedac. There is no parameter for the TIntegerfield that allows me to make it primary. There is also no parameter of the TFDTable where I can choose the primary field out of all available fields.
I get that it may be possible doing it in code & combining it with my design time table but that beats the whole purpose of doing it all in design time.
Earlier I did have a auto-inc ID in my table, and this was automatically set to primary key. I deleted this field now because I need another integer to be the primary.
Also I can't find information about the primary key & TFDTable on the official Embacadero website.
It's best to experiment with this using a new table in your database and a minimal new Delphi project.
Update: See below for database DDL and Form's DFM.
You need to have your ID field marked as a primary key in your database.
After you've added an FDConnection and an FDTable to your project, select the FDTable's TableName from the drop down list. Then, click in the FDTable's IndexName field and you should find an automatically-named index on the table's Primary Key. Just select it so that the IndexName takes its value. That's all there is to it.
For the table created using the DDL below, the IndexName property of the FDTable appears as sqlite_autoindex_test_1
If you then dbl-click the FDTable and use the pop-up Fields editor to set up persistent fields on the FDTable and then select your ID field, you should find that if you examine its ProviderFlags, they should include pfInKey, which is what tells FireDAC to use the field as the table's primary key when generating the SQL to update it, do inserts, etc.
You should find that the ID field's Required field is autmatically set to True, btw.
If you want to supply the ID Field's value yourself when adding a new record, use the table's OnNewRecord to generate the ID value an assign it to the field.
DDL for test Sqlite database
create table test(
id int not null primary key,
AName nchar(12)
)
Project DFM extract
object Form2: TForm2
object DBGrid1: TDBGrid
DataSource = DataSource1
end
object DBNavigator1: TDBNavigator
DataSource = DataSource1
end
object FDConnection1: TFDConnection
Params.Strings = (
'Database=D:\aaad7\sqlite\MADB1.sqlite'
'DriverID=SQLite')
Connected = True
LoginPrompt = False
end
object DataSource1: TDataSource
DataSet = FDTable1
end
object FDTable1: TFDTable
IndexName = 'sqlite_autoindex_test_1'
Connection = FDConnection1
UpdateOptions.UpdateTableName = 'test'
TableName = 'test'
object FDTable1id: TIntegerField
FieldName = 'id'
Origin = 'id'
ProviderFlags = [pfInUpdate, pfInWhere, pfInKey]
Required = True
end
object FDTable1AName: TWideStringField
FieldName = 'AName'
Origin = 'AName'
FixedChar = True
Size = 12
end
end
end

Update always encrypted column from decrypted column

I would like to encrypt an existing database column with always encrypted. My project is a ASP.NET project using code first and database is SQL Server. The database has already data. I created a migration to achieve my goal.
First I tried to alter the column type, using the following.
ALTER TABLE [dbo].[TestDecrypted] ALTER COLUMN [FloatCol] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
I got the following error.
Operand type clash: float is incompatible with float encrypted with (encryption_type = 'RANDOMIZED', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'TestEncrypt')
Then I decided to created another column and migrate the data.
ALTER TABLE [dbo].[TestDecrypted] ADD [FloatCol2] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = [FloatCol]
And I got the same error.
After I looked at this, I noticed that it is possible to insert data like the following
DECLARE #floatCol FLOAT = 1.1
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #floatCol
But if I try to obtain the value from my existing column, it fails.
DECLARE #floatCol FLOAT = (SELECT TOP 1 FloatCol FROM TestDecrypted)
UPDATE [dbo].[TestDecrypted] SET FloatCol2 = #floatCol
The error follows.
Encryption scheme mismatch for columns/variables '#floatCol'. The encryption scheme for the columns/variables is (encryption_type = 'PLAINTEXT') and the expression near line '4' expects it to be (encryption_type = 'RANDOMIZED', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = 'CEK_Auto1', column_encryption_key_database_name = 'TestEncrypt').
Does anyone knows how can I achieve my goal?
Update 1
#Nikhil-Vithlani-Microsoft did some interesting suggestions.
Always Encrypted Wizard in SSMS - I would like to achieve my goal with code first migrations, so this idea does not fit.
SqlBulkCopy - It does not work inside migrations, because the new column will only exist after all 'Up' method is run. Therefore we cannot insert data into this column in this way inside this method.
Anyway, his suggestions drove me to another attempt: obtain the decrypted values and update the encrypted column with them.
var values = new Dictionary<Guid, double>();
var connectionString = ConfigurationManager.ConnectionStrings["MainDb"].ConnectionString;
using (var sourceConnection = new SqlConnection(connectionString))
{
var myCommand = new SqlCommand("SELECT * FROM dbo.TestDecrypted", sourceConnection);
sourceConnection.Open();
using (var reader = myCommand.ExecuteReader())
{
while (reader.Read())
{
values.Add((Guid)reader["Id"], (double)reader["FloatCol"]);
}
}
}
Sql("ALTER TABLE [dbo].[TestDecrypted] ADD [FloatCol2] [float] ENCRYPTED WITH (COLUMN_ENCRYPTION_KEY = [CEK_Auto1], ENCRYPTION_TYPE = Randomized, ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256') NULL");
foreach (var valuePair in values)
{
// The error occurs here
Sql($#"DECLARE #value FLOAT = {valuePair.Value}
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #value WHERE Id = '{valuePair.Key}'");
}
In fact, I did not try to create another column and to migrate the data, as mentioned in an example above. I tried it only on SSMS.
And now I got a different error.
Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
I tried to do it without encrypting the new column, and it worked properly.
Any idea why this error occurs?
You will have to do the always encrypted related migration outside of entity framework. This blog should help
https://blogs.msdn.microsoft.com/sqlsecurity/2015/08/27/using-always-encrypted-with-entity-framework-6/
If you want to encrypt an existing column, you can use Always Encrypted Wizard in SSMS, or use this article that explains how to migrate existing data.
Also, please note that doing bulk inserts through a C# (.NET 4.6.1+ client) app is supported.
You can do this in c# using SqlBulkCopy specifically using SqlBulkCopy.WriteToServer(IDataReader) Method.
Create a new table (encryptedTable) with the same schema as that of your plaintext table (unencryptedTable) but with the encryption turned on for the desired columns.
Do select * from unencryptedTable to load the data in a SqlDataReader then use SqlBulkCopy to load it to the encryptedTable using SqlBulkCopy.WriteToServer(IDataReader) Method
For example,
Plaintext Table
CREATE TABLE [dbo].[Patients](
[PatientId] [int] IDENTITY(1,1),
[SSN] [char](11) NOT NULL)
Encrypted Table
CREATE TABLE [dbo].[Patients](
[PatientId] [int] IDENTITY(1,1),
[SSN] [char](11) COLLATE Latin1_General_BIN2
ENCRYPTED WITH (ENCRYPTION_TYPE = DETERMINISTIC,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256',
COLUMN_ENCRYPTION_KEY = CEK1) NOT NULL)
As for why your method does not work,
when you use parameterization for always encrypted, the right hand side (RHS) of the declare statement needs to be a literal. Because the driver will identify the literal and encrypt it for you. So, the following will not work, since RHS is a sql expression and cannot be encrypted by the driver
DECLARE #floatCol FLOAT = (SELECT TOP 1 FloatCol FROM TestDecrypted)
UPDATE [dbo].[TestDecrypted] SET FloatCol2 = #floatCol
Update:
The following code will not work because parameterization for Always Encrypted only applies to SSMS
foreach (var valuePair in values)
{
// The error occurs here
Sql($#"DECLARE #value FLOAT = {valuePair.Value}
UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #value WHERE Id = '{valuePair.Key}'");
}
However, if you rewrite your code as follows, that should work
foreach (var valuePair in values)
{
SqlCommand cmd = _sqlconn.CreateCommand();
cmd.CommandText = #"UPDATE [dbo].[TestDecrypted] SET [FloatCol2] = #FloatVar WHERE Id = '{valuePair.Key}'");";
SqlParameter paramFloat = cmd.CreateParameter();
paramFloat.ParameterName = #"#FloatVar";
paramFloat.DbType = SqlDbType.Float;
paramFloat.Direction = ParameterDirection.Input;
paramFloat.Value = floatValue;
cmd.Parameters.Add(paramFloat);
cmd.ExecuteNonQuery();
}
Hope that helps, if you have additional question, please leave them in the comments.

how to pass local variable to a linq query

I have the following code in which I am passing a local variable to a linq query for a specific record, after that record I want to check whether there is a record according to that id or not.
First it gives me the error "Cannot implicitly convert type int to bool"
Second if I want to count the rows in this query or want to check whether there is a row or not, how will I do that, here is my code:
int J_Job_ID = Convert.ToInt32(Request.QueryString["J_Job_ID"]);
//Check If this ID exists in the database
var query = from m in JE.J_Posted_Jobs_Tbl
where m.J_Job_ID = Convert.ToInt32(J_Job_ID)
select m;
it should be
where m.J_Job_ID == Convert.ToInt32(J_Job_ID)
as for count
query.Count()

EntityFramework using with database foreign key

Actually I spend whole day on the EntityFramework for foreign key.
assume we have two table.
Process(app_id,process_id)
LookupProcessId(process_id, process_description)
you can understand two tables with names, first table ,use process_id to indicate every application, and description is in the seoncd table.
Actually i try many times and figure out how to do inquery: it was like
Dim result = (from x in db.Processes where x.LookupProcess is (from m in db.LookupProcessIds where descr = "example" select m).FirstOrDefault() select x).FirstOrDefault()
First I want to ask is there easier way to do it.
Second i want to ask question is about insert
p As New AmpApplication.CUEngData.Process
p.app_id=100
p.LookupProcess = (from m in db.LookupProcessIds where descr = "example" select m).FirstOrDefault()
db.AddToProcesses(p)
db.SaveChanges()
from appearance it looks fine, but it give me error says
Entities in 'AmpCUEngEntities.Processes' participate in the 'FK_Process_LookupProcess' relationship. 0 related 'LookupProcess' were found. 1 'LookupProcess' is expected.
can i ask is that insert wrong? and is that my query correct?
For your first question:
Dim result = (from x in db.Processes
where x.LookupProcess.descr = "example"
select x).FirstOrDefault()
Actually, you missed some concepts from DataEntityModel, and its Framework. To manipulate data, you have to call object from contextual point of view. Those allow you to specify to the ObjectStateManager the state of an DataObject. In your case, if you have depending data from FK, you will have to add/update any linked data from leaf to root.
This example demonstrate simple (no dependances) data manipulation. A select if existing and an insert or update.
If you want more info about ObjectStateManager manipulation go to http://msdn.microsoft.com/en-us/library/bb156104.aspx
Dim context As New Processing_context 'deseign your context (this one is linked to a DB)
Dim pro = (From r In context.PROCESS
Where r.LOOKUPPROCESS.descr = LookupProcess.descr
Select r).FirstOrDefault()
If pro Is Nothing Then 'add a new one
pro = New context.PROCESS With {.AP_ID = "id", .PROCESS_ID = "p_id"}
context.PROCESS.Attach(pro)
context.ObjectStateManager.ChangeObjectState(pro, System.Data.EntityState.Added)
Else
'update data attibutes
pro.AP_ID = "id"
pro.PROCESS_ID = "p_id"
context.ObjectStateManager.ChangeObjectState(pro, System.Data.EntityState.Modified)
'context.PROCESS.Attach(pro)
End If
context.SaveChanges()
I hope this will help. Have a nice day!
For your first question, to expand on what #jeroenh suggested:
Dim result = (from x in db.Processes.Include("LookupProcess")
where x.LookupProcess.descr = "example"
select x).FirstOrDefault()
The addition of the Include statement will hydrate the LookupProcess entities so that you can query them. Without the Include, x.LookupProcess will be null which would likely explain why you got the error you did.
If using the literal string as an argument to Include is not ideal, see Returning from a DbSet 3 tables without the error "cannot be inferred from the query" for an example of doing this using nested entities.
For your second question, this line
p.LookupProcess = (from m in db.LookupProcessIds
where descr = "example" select m).FirstOrDefault()
Could cause you problems later on because if there is no LookupProcessId with a process_description of "example", you are going to get null. From MSDN:
The default value for reference and nullable types is null.
Because of this, if p.LookupProcess is null when you insert the entity, you will get the exception:
Entities in 'AmpCUEngEntities.Processes' participate in the 'FK_Process_LookupProcess' relationship. 0 related 'LookupProcess' were found. 1 'LookupProcess' is expected.
To avoid this kind of problem, you will need to check that p.LookupProcess is not null before it goes in the database.
If Not p.LookupProcess Is Nothing Then
db.AddToProcesses(p)
db.SaveChanges()
End If

Resources