I'm very sorry for the title, can't figure out a better one. Any amendment is greatly appreciated!
Says i have these tables :
Header(id int identity(1,1), Date datetime, ...)
Detail(id int identity(1,1), HeaderID int, MaterialID nvarchar(24), Quantity float, AccountID nvarchar(20), Amount float)
Ledger(HeaderID, AccountID nvarchar(20), Amount float)
InventoryTransactionDetail(HeaderID, MaterialID nvarchar(24), Quantity float)
Here is how this work :
Header contains general information of a voucher
Detail contains voucher's detail records
We will then analyze Detail's record and produce data for Ledger and Inventory
For example :
Insert into Header(Date, ...) values (getdate(), ...)
Assuming the newly created header ID is 1
Insert into Detail(HeaderID, MaterialID, Quantity, AccountID, Amount)
values(1, 'MAT1', 50, '1561', 500000)
After analyzing, we have Ledger and InventoryTransactionDetail's records:
Insert into Ledger(HeaderID, AccountID, Amount)
values(1, '1561', 500000)
Insert into InventoryTransactionDetail(HeaderID, MaterialID, Quantity)
values(1, 'MAT1', 50)
So if there is any changes was made on the voucher, i will :
Update the header and the detail
Delete from Ledger and Inventory, and insert new analyzed records into them
Sound simple, right? I can achieve this using T-sql in just a glance, but doing this using EF is giving me a nightmare, i keep getting errors i can't figure out why and how to fix it!
So what i want to ask is, am i doing this the right way?
By the way, this is one of the errors i'm getting :
A referential integrity constraint violation occurred: The property
values that define the referential constraints are not consistent
between principal and dependent objects in the relationship.
Line of error (the line before the last : Attach(VH) ) :
using (var context2 = new GModel())
{
List<Ledger> AJToCheck = (from a in context2.Ledger where a.VHID == VH.ID select a).ToList();
foreach (Ledger DetailToCheck in AJToCheck)
{
context2.DeleteObject(DetailToCheck);
}
List<ITDetail> ITToCheck = (from a in context2.ITDetail where a.VHID == VH.ID select a).ToList();
foreach (ITDetail DetailToCheck in ITToCheck)
{
context2.DeleteObject(DetailToCheck);
}
context2.SaveChanges();
}
using (var context = new GModel())
{
if (VH.ID == 0 || VH.ID == null)
{
VH.State = State.Added;
context.VoucherHeader.AddObject(VH);
}
else
{
VH.State = State.Modified;
int counterID = -1;
foreach (var voucherDetail in VH.VoucherDetail)
{
if (voucherDetail.ID == 0)
{
counterID--;
voucherDetail.State = State.Added;
voucherDetail.ID = counterID;
}
else voucherDetail.State = State.Modified;
}
counterID = -1;
foreach (var Ledger in VH.Ledger)
{
counterID--;
Ledger.State = State.Added;
Ledger.ID = counterID;
}
counterID = -1;
foreach (var itDetail in VH.ITDetail)
{
counterID--;
itDetail.State = State.Added;
itDetail.ID = counterID;
}
context.VoucherHeader.Attach(VH);
context.ObjectStateManager.ChangeObjectState(VH, StateHelpers.GetEquivalentEntityState(VH.State));
This appears to be much more complex than it needs to.
This error generally occurs when the entity in the many part of a one to many relationship is being attached with a foreign key different from the parent object.
In your case the SQL examples you show, you insert into the dependent tables and only set the hearderID, but in all your EF examples you assign negative numbers to the ID properties of all your navigation properties?
To repeat what you have with SQL you would be better off just going straight to the attach of your VH rather than attempting to manipulate the navigation properties.
When VH is attached, any of the navigation properties in the graph that didn't exist (IDs == 0) will be automatically added (inserted). Your marking both the VH and its navigation properties as modified is unnecessary, and likely contributes to your problem.
Try this instead:
if (VH.ID == 0 || VH.ID == null)
{
VH.State = State.Added;
context.VoucherHeader.AddObject(VH);
}
else
{
VH.State = State.Modified;
context.VoucherHeader.Attach(VH);
context.ObjectStateManager.ChangeObjectState(VH, StateHelpers.GetEquivalentEntityState(VH.State));
}
Also take a look at http://msdn.microsoft.com/en-us/magazine/dn166926.aspx for that version of EF
Related
I've created a database trigger on my table that updates a field in the table after an insert. When doing an insert using EF I'll get the ID and the number. On the database I've created this code:
create table Things (
ID int primary key identity not null,
Number nvarchar(20)
);
create trigger UpdateThingsNumberTrigger on Things
after insert
as
begin
declare #month nvarchar(2);
select #month = cast(month(getdate()) as nvarchar(2));
declare #code nvarchar(15);
select #code = cast(year(getdate()) as nvarchar(4)) +
'.' +
replicate('0', 2 - len(#month)) +
#month +
'.';
declare #max nvarchar(20);
select #max = t.ID
from Things t
where ID like #code + '%';
with CTE_UPD as
(
select
replicate('0',
4 -
len(cast(coalesce(cast(right(#max, 4) as int), 0) + row_number() over (order by ins.ID) as nvarchar(4)))) +
cast(coalesce(cast(right(#max, 4) as int), 0) + row_number() over (order by ins.ID) as nvarchar(4)) as NextNo,
ID
from Things ins
)
update Things
set Number = #code + NextNo
from Things t inner join CTE_UPD ins on ins.ID = t.ID;
end
Note: For the logical flaw inside the trigger, I'll refer to Create an incremental number with year and month without updating the entire table using a trigger on Database Administrators SE.
This part of my code works fine, ignoring the logical flaw inside the trigger… The problem I'll try to solve in this question, is when I insert a thing in my table from Entity Framework (database first). There is my code and the output:
using (Database db = new Database())
{
Thing thing = new Thing(); // --> just an empty constructor.
db.Entry(thing).State = EntityState.Added;
await db.SaveChangesAsync();
Console.WriteLine($"ID = {thing.ID}");
Console.WriteLine($"Number = {thing.Number}");
}
// Output:
// ID = 1
// Number =
In the background EF is doing this code on the server when calling SaveChangesAsync():
INSERT [dbo].[Things]([Number])
VALUES (NULL)
SELECT [ID]
FROM [dbo].[Things]
WHERE ##ROWCOUNT > 0 AND [ID] = scope_identity()
Now can EF update the ID in the C# object. But how could I get the number without using code below before closing the using block?
Thing recentlyInsertedThing = await db.Things.FindAsync(thing.ID);
I've found it to get the ID and the Number without writing a 2nd select statement. This is my code:
using (Database db = new Database())
{
Thing thing = new Thing();
string sql = #"insert into Things()
values ();
select ID, Number
from Things
where ##rowcount > 0 and ID = scope_identity();";
KeyMapper recentlyRecevedKeys = await db
.Database
.SqlQuery<KeyMapper>(sql)
.FirstAsync();
thing.ID = recentlyRecevedKeys.ID;
thing.Number = recentlyRecevedKeys.Number;
}
// Nested class
private class KeyMapper
{
public int ID { get; set; }
public string Number { get; set; }
}
I have a few table structure look as below:
CREATE TABLE Person
(
PersonID INT PRIMARY KEY,
Name NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
CREATE TABLE Info
(
InfoID INT PRIMARY KEY,
PersonID INT,
Info NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
CREATE TABLE Setting
(
SettingID INT PRIMARY KEY,
PersonID INT,
Setting NVARCHAR(255),
LastUpdatedBy INT,
LastUpdatedDate DATETIME
);
I face a new procedure to follow that if there is any updates on Info or Setting table, I will need to do relevant updates to Person table on columns LastUpdatedBy and LastUpdatedDate.
What first come to my mind is to create a SQL trigger that automatically update Person table when Info or Setting table does. But take a quick glance through for a few articles stating that a SQL trigger should be avoided as it's an very expensive process when creating it,
While some people recommends to change in application code. For an example,
using (var db = new DbContext())
{
var result = db.Info.SingleOrDefault(x => x.InfoID == infoID);
if (result != null)
{
result.Info = "Some new value";
result.LastUpdatedBy = userID;
result.LastUpdatedDate = DateTime.UtcNow;
db.SaveChanges();
}
}
need to change and become like this.
using (var db = new DbContext())
{
var result = db.Info.SingleOrDefault(x => x.InfoID == infoID);
if (result != null)
{
result.Info = "Some new value";
result.LastUpdatedBy = userID;
result.LastUpdatedDate = DateTime.UtcNow;
var person = db.Person.SingleOrDefault(x => x.PersonID == result.PersonID);
if (person != null)
{
person.LastUpdatedBy = result.LastUpdatedBy;
person.LastUpdatedDate = result.LastUpdatedDate;
}
db.SaveChanges();
}
}
in reality, the application code is massive, a lot of code modification need to be made.
Assume there are 30+ tables, and each of them contain at least 100k of records. If creating of triggers are possible, it will be as the following:
CREATE TRIGGER TriggerName ON dbo.Info
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Person
SET LastUpdatedBy = INSERTED.LastUpdatedBy ,
LastUpdatedDate = INSERTED.LastUpdatedDate
FROM INSERTED
WHERE dbo.Person.PersonID = INSERTED.PersonID
END
GO
Is the SQL trigger should really be avoided in this scenario? Please explain based on your answer if can. Any alternative solution is welcome, performance first.
Trigger is optimal (from a performance perspective) here; it's simply like running an update statement on a bunch of rows from the front end code. I don't see why you think there is a performance penalty. Your trigger code should look more like this though:
CREATE TRIGGER TriggerName ON dbo.Info
AFTER INSERT, UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE dbo.Person
SET LastUpdatedBy = INSERTED.LastUpdatedBy ,
LastUpdatedDate = INSERTED.LastUpdatedDate
FROM dbo.Person
INNER JOIN
INSERTED
ON dbo.Person.PersonID = INSERTED.PersonID
END
GO
There are other ways, such as making a Stored procedure that updates all tables in a transaction, or updating front end data access layer (if your front end has a lot to update, it implies it is structured wrong: one place should have responsibility for writing to this table. If your front end code has update statements peppered all through it, well.. that's a bad design) so a dedicated class maintains these two tables properly..
Right now I'd say a trigger is your easiest way out of the problem.. they aren't well liked, though not because of performance, but because they start to add confusing consequences.. imagine you as a c# developer with limited database experience, didn't know what a trigger was, and you're complaining "every time I update just this one table, all these other 27 tables change by magic! What's going on? Am I going crazy or what?" - triggers break rules like "keep all your data updating code in one place" and this is why people who engineer systems where specific parts have specific jobs, don't like them
I have to import a hundreds records to database from Excel.
Each record has to be verified:
Against duplicate
Has to has foreign key in another table
I’m wondering how should I do this with the highest performance. I know that I shouldn’t use db.SaveChanges(); after each record so after verification - I’m adding each record to temporary list (var recordsToAdd), and I’m saving that list after all.
Please check my code below, is this good approach to do this?
using (var db = new DbEntities())
{
var recordsToAdd = new List<User>();
for (var row = 2; row <= lastRow; row++)
{
var newRecord = new User
{
Id = Int32.Parse(worksheet.Cells[idColumn + row].Value.ToNullSafeString()),
FirstName = worksheet.Cells[firstNameColumn + row].Value.ToNullSafeString(),
LastName = worksheet.Cells[lastNameColumn + row].Value.ToNullSafeString(),
SerialNumber = worksheet.Cells[serialNumber + row].Value.ToNullSafeString()
};
bool exists = db.User.Any(u => u.Id == newRecord.Id) || recordsToAdd.Any(u => u.Id == newRecord.Id);
if (!exists)
{
bool isSerialNumberExist = db.SerialNumbers.Any(u => u.SerialNumber == newRecord.SerialNumber);
if (isSerialNumberExist)
{
recordsToAdd.Add(newRecord);
}
else
{
resultMessages.Add(string.Format("SerialNumber doesn't exist"));
}
}
else
{
resultMessages.Add(string.Format("Record already exist"));
}
}
db.User.AddRange(recordsToAdd);
db.SaveChanges();
}
First of all let's separate the code into two parts. First part is creating a list of valid User records to be inserted. Second part is inserting those records to the database (last two lines of your code).
Assuming you are using EntityFramework as your ORM, second part may be optimized by bulk inserting the records. It has many existing solutions that can be easily found. (example)
There are some suggestions concerning the first part.
Load user ids in a HashSet or Dictionary. These data structures are optimized for searching. var userDbIds = new HashSet<int>(db.User.Select(x => x.Id));. You will quickly check if id exists without making a request to DB.
Do the same for serialNumber. var serialNumbers = new HashSet<string>(db.SerialNumber.Select(x => x.SerialNumber)); assuming that type of SerialNumber property is string.
Change the type of your recordToAdd variable to be Dictionary<int, User> for the same reason.
In the check would look like this:
bool exists = userDbIds.Contains(newRecord.Id) || recordsToAdd.ContainsKey(newRecord.Id);
if (!exists)
{
bool isSerialNumberExist = serialNumbers.Contains(newRecord.SerialNumber);
if (isSerialNumberExist)
{
recordsToAdd[newRecord.Id] = newRecord;
}
else
{
resultMessages.Add(string.Format("SerialNumber doesn't exist"));
}
}
else
{
resultMessages.Add(string.Format("Record already exist"));
}
One way to improve the performance is to minimize the db calls and linear searches by using a fast lookup data structures for performing the verification - HashSet<string> for Id and Dictionary<string, bool> for SerialNumber:
using (var db = new DbEntities())
{
var recordsToAdd = new List<User>();
var userIdSet = new HashSet<string>();
var serialNumberExistsInfo = new Dictionary<string, bool>();
for (var row = 2; row <= lastRow; row++)
{
var newRecord = new User
{
Id = Int32.Parse(worksheet.Cells[idColumn + row].Value.ToNullSafeString()),
FirstName = worksheet.Cells[firstNameColumn + row].Value.ToNullSafeString(),
LastName = worksheet.Cells[lastNameColumn + row].Value.ToNullSafeString(),
SerialNumber = worksheet.Cells[serialNumber + row].Value.ToNullSafeString()
};
bool exists = !userIdSet.Add(newRecord.Id) || db.User.Any(u => u.Id == newRecord.Id);
if (!exists)
{
bool isSerialNumberExist;
if (!serialNumberExistsInfo.TryGetValue(newRecord.SerialNumber, out isSerialNumberExist))
serialNumberExistsInfo.Add(newRecord.SerialNumber, isSerialNumberExist =
db.SerialNumbers.Any(u => u.SerialNumber == newRecord.SerialNumber));
if (isSerialNumberExist)
{
recordsToAdd.Add(newRecord);
}
else
{
resultMessages.Add(string.Format("SerialNumber doesn't exist"));
}
}
else
{
resultMessages.Add(string.Format("Record already exist"));
}
}
db.User.AddRange(recordsToAdd);
db.SaveChanges();
}
It would be most efficient to use a Table-Valued Parameter instead of LINQ. That way you can handle this in a set-based approach that is a single connection, single stored procedure execution, and single transaction. The basic setup is shown in the example code I provided in the following answer (here on S.O.):
How can I insert 10 million records in the shortest time possible?
The stored procedure can handle both validations:
don't insert duplicate records
make sure that SerialNumber exists
The User-Defined Table Type (UDTT) would be something like:
CREATE TYPE dbo.UserList AS TABLE
(
Id INT NOT NULL,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NULL,
SerialNumber VARCHAR(50) NOT NULL
);
-- Uncomment the following if you get a permissions error:
-- GRANT EXECUTE ON TYPE::[dbo].[UserList] TO [ImportUser];
GO
The stored procedure (executed via SqlCommand.ExecuteNonQuery) would look something like:
CREATE PROCEDURE dbo.ImportUsers
(
#NewUserList dbo.UserList READONLY
)
AS
SET NOCOUNT ON;
INSERT INTO dbo.User (Id, FirstName, LastName, SerialNumber)
SELECT tmp.Id, tmp.FirstName, tmp.LastName, tmp.SerialNumber
FROM #NewUserList tmp
WHERE NOT EXISTS (SELECT *
FROM dbo.User usr
WHERE usr.Id = tmp.[Id])
AND EXISTS (SELECT *
FROM dbo.SerialNumbers sn
WHERE sn.SerialNumber = tmp.[SerialNumber]);
The stored procedure above simply ignores the invalid records. If you need notification of the "errors", you can use the following definition (executed via SqlCommand.ExecuteReader):
CREATE PROCEDURE dbo.ImportUsers
(
#NewUserList dbo.UserList READONLY
)
AS
SET NOCOUNT ON;
CREATE TABLE #TempUsers
(
Id INT NOT NULL,
FirstName NVARCHAR(50) NOT NULL,
LastName NVARCHAR(50) NULL,
SerialNumber VARCHAR(50) NOT NULL,
UserExists BIT NOT NULL DEFAULT (0),
InvalidSerialNumber BIT NOT NULL DEFAULT (0)
);
INSERT INTO #TempUsers (Id, FirstName, LastName, SerialNumber)
SELECT tmp.Id, tmp.FirstName, tmp.LastName, tmp.SerialNumber
FROM #NewUserList tmp;
-- Mark existing records
UPDATE tmp
SET tmp.UserExists = 1
FROM #TempUsers tmp
WHERE EXISTS (SELECT *
FROM dbo.User usr
WHERE usr.Id = tmp.[Id]);
-- Mark invalid SerialNumber records
UPDATE tmp
SET tmp.InvalidSerialNumber = 1
FROM #TempUsers tmp
WHERE tmp.UserExists = 0 -- no need to check already invalid records
AND NOT EXISTS (SELECT *
FROM dbo.SerialNumbers sn
WHERE sn.SerialNumber = tmp.[SerialNumber]);
-- Insert remaining valid records
INSERT INTO dbo.User (Id, FirstName, LastName, SerialNumber)
SELECT tmp.Id, tmp.FirstName, tmp.LastName, tmp.SerialNumber
FROM #TempUsers tmp
WHERE tmp.UserExists = 0
AND tmp.InvalidSerialNumber = 0;
-- return temp table to caller as it contains validation info
SELECT tmp.Id, tmp.FirstName, tmp.LastName, tmp.SerialNumber,
tmp.UserExists, tmp.InvalidSerialNumber
FROM #TempUsers tmp
-- optionally only return records that had a validation error
-- WHERE tmp.UserExists = 1
-- OR tmp.InvalidSerialNumber = 1;
When this version of the stored procedure completes, cycle through SqlDataReader.Read() to get the validation info.
I need help, I have a SQL table (Order) has a field ID int Identity, and another table (OrderDetail) where one of his fields is this ID
The Order table struct is:
ID (PK, int, Identity, Not Null)
Service (char(10), Not Null)
TypeReposition (char(10), Null)
And the OrderDetail table struct is:
IDOrder (PK, FK, int, Not Null) <- this must be equal to corresponding ID in Order
Status (PK, char(25), Not Null)
StatusDate (PK, datetime, Not Null)
to insert a new record in the Order table, I make this:
Order newOrder = new Order();
newOrder.Service = ((TypeService)cbTypeService.SelectedItem).Service;
newOrder.TypeReposition = null;
OrderDomainDataSource.DataView.Add(newOrder);
OrderDomainDataSource.DomainContext.SubmitChanges(so =>
{
if (so.HasError)
{
//Handle errors from submit
so.MarkErrorAsHandled();
}
if (OrderDomainDataSource.CanLoad)
OrderDomainDataSource.Load();
}, null);
The ID is generated automatically when the insert is done.
Immediately after this, I need insert the corresponding OrderDetail record, but for that I need to know the ID generated for the Order record, how I know this??
I'm using Silverlight 5 with WCF Ria Services
I think I found a solution:
newOrderDetail.IDOrder = ((Order)OrderDomainDataSource.DataView.CurrentItem).ID;
Because OrderDomainDataSource.DataView.CurrentItem, after SubmitChanges, points to the Order record that I added previously
I know this is a common issue and I've read about it here and elsewhere but in most cases the problem is that a row already exists in the database (which I'm checking for...). In my case it's a new db with empty tables (well.. except for Publishers table that has one row in Id = 0), the code goes like this (Entity Framework V5):
public void InsertPublisherResult(PublisherResult pResult)
{
using (mydbEntities e = new mydbEntities())
{
if (e.Publishers.Any(c => c.Name == pResult._Publisher.Name))
continue;
e.Publishers.Add(scraperResult._Publisher);
foreach (RelatedPublisher rp in pResult._RelatedPublishers)
{
e.RelatedPublishers.Add(rp);
}
foreach (Download d in pResult._Downloads)
{
e.Downloads.Add(d);
}
foreach (PublisherDomain pd in pResult._PublisherDomains)
{
e.PublisherDomains.Add(pd);
}
e.SaveChanges();
}
}
The rest of the tables (RelatedPublishers, Downloads and PublisherDomains) are empty, because they have non of the mentioned objects in the first pResult, with Primary Key - Id which is the defined entity key.
The first pResult is skipped because it exists, and the second one throws the exception on the PK violation.
Could it be that I'm trying to insert the 2nd pResult in the first row (id=0 on Publishers)? and if not, then what am I doing wrong?
Thanks to the commenter that asked me if Id is auto incremented I checked it and got to the answer:
The column wasn't auto incremented and I had to drop the column or in my case I dropped the table and created it again using the correct SQL statement:
CREATE TABLE [mydb].[dbo].[Publishers]
(
Id Integer IDENTITY(1,1) primary key,
PublisherGuid uniqueidentifier,
Name nvarchar(100),
Link nvarchar(100)
);
After that I Updated the Model from Database and it worked! That comment saved me lots of time...