A perfect blend of all things Dot Net
When learning LINQ-to-SQL, it’s not immediately obvious how to do an update. Querying is easy, and there are methods for inserting and deleting. Updating usually occurs by modifying an object already known to the DataContext and then calling SubmitChanges on the context.
var product = (from p in dataContext.Products where p.ProductID == 1 select p).Single(); product.Name = "Richard’s product"; dataContext.SubmitChanges();
It’s nice to see that MSDN documentation actually addresses the obvious arising question:
Q. Can I update table data without first querying the database?
A. Although LINQ to SQL does not have set-based update commands, you can use either of the following techniques to update without first querying:
- Use ExecuteCommand to send SQL code.
- Create a new instance of the object and initialize all the current values (fields) that affect the update. Then attach the object to the DataContext by using Attach and modify the field you want to change.
If you ask me, using an ExecuteCommand defeats the object of LINQ-to-SQL. After all, we’re using it as a data-access-layer to generate entities in code based on the data schema. The last thing we want to have to do is start writing strings of SQL.
So that’s out.
The second option is to use the Attach method on the table we’re updating. I’ve tried to use the Attach method before, but it didn’t work.
To see what I mean, try this in LINQPad (using the AdventureWorks database from CodePlex):
var product = new Product(); product.ProductID = 1; product.Name = "Richard’s product"; Products.Attach(product); var changeSet = GetChangeSet(); changeSet.Dump();
You’ll see that there are no changes to make, so no updates are made.
Now, move the line of code that does the update after the call to Attach, so as to attach the product first before updating the field:
var product = new Product(); product.ProductID = 1; Products.Attach(product); //Notice we’re doing this laterproduct.Name = "Richard’s product"; var changeSet = GetChangeSet(); changeSet.Dump();
The changeset now shows that one update is ready to be made. Unfortunately, if you call SubmitChanges, you get an exception:
SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM.
The MSDN documentation isn’t so clear here. What do we need to set to get it to work?
The SQL being sent to the database is instructive:
UPDATE [Production].[Product] SET [Name] = @p9 WHERE ([ProductID] = @p0) AND ([Name] IS NULL) AND ([ProductNumber] IS NULL) AND (NOT ([MakeFlag] = 1)) AND (NOT ([FinishedGoodsFlag] = 1)) AND ([Color] IS NULL) AND ([SafetyStockLevel] = @p1) AND ([ReorderPoint] = @p2) AND ([StandardCost] = @p3) AND ([ListPrice] = @p4) AND ([Size] IS NULL) AND ([SizeUnitMeasureCode] IS NULL) AND ([WeightUnitMeasureCode] IS NULL) AND ([Weight] IS NULL) AND ([DaysToManufacture] = @p5) AND ([ProductLine] IS NULL) AND ([Class] IS NULL) AND ([Style] IS NULL) AND ([ProductSubcategoryID] IS NULL) AND ([ProductModelID] IS NULL) AND ([SellStartDate] = @p6) AND ([SellEndDate] IS NULL) AND ([DiscontinuedDate] IS NULL) AND ([rowguid] = @p7) AND ([ModifiedDate] = @p8) – @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] – @p1: Input SmallInt (Size = 0; Prec = 0; Scale = 0) [0] – @p2: Input SmallInt (Size = 0; Prec = 0; Scale = 0) [0] – @p3: Input Decimal (Size = 0; Prec = 29; Scale = 4) [0] – @p4: Input Decimal (Size = 0; Prec = 29; Scale = 4) [0] – @p5: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
The problem is now that LINQ-to-SQL tables automatically use optimistic concurrency, and for some reason (maybe only in LINQPad) the SellStartDate is not set, and neither are the rowguid or ModifiedDate fields. In any case, either we have to turn off optimistic concurrency, or set all the field values ourselves.
Unfortunately, by default all fields are used for optimistic concurrency, so you have to set values for all fields. If you were using ASP.Net you could keep a copy of the variables in the session state or something, but at least it saves you a round trip to the database.
Here’s the code that actually does perform an update:
var product = new Product(); product.ProductID = 1; product.Name = "Adjustable Race"; product.ProductNumber = "AR-5381"; product.MakeFlag = false; product.FinishedGoodsFlag = false; product.Color = null; product.SafetyStockLevel = 1000; product.ReorderPoint = 750; product.StandardCost = 0.0000M; product.ListPrice = 0.0000M; product.Size = null; product.SizeUnitMeasureCode = null; product.WeightUnitMeasureCode = null; product.Weight = null; product.DaysToManufacture = 0; product.ProductLine = null; product.Class = null; product.Style = null; product.ProductSubcategoryID = null; product.ProductModelID = null; product.SellStartDate = DateTime.Parse("6/1/1998 12:00:00 AM"); product.SellEndDate = null; product.DiscontinuedDate = null; product.Rowguid = new Guid("694215b7-08f7-4c0d-acb1-d734ba44c0c8"); product.ModifiedDate = DateTime.Parse("3/11/2004 10:01:36.827 AM"); Products.Attach(product); // Make the change here product.Name = "Richard’s product"; SubmitChanges();
So you can avoid two trips to the database by doing the update manually and using the Attach method on the table you are updating.
Mind you, I’m not sure that’s worth it.
If you want to know more about updating LINQ-to-SQL objects with the DataContext, I recommend Pro LINQ: Language Integrated Query in C# 2008. It’s got 6 chapters just about LINQ-to-SQL, the DataContext, and concurrency issues.
P.S. In case you’re interested, I generated some of that code in LINQPad using the following code (it doesn’t work for dates or Guids, before anyone complains):
var product = (from p inProducts
wherep.ProductID == 1
selectp).Single();
var fields = from field inproduct.GetType().GetFields()
select new{
Name = field.Name,
Value = field.GetValue(product),
TypeName = field.FieldType.Name };
foreach (var field infields)
Console.WriteLine("product.{0} = {1};", field.Name,
field.TypeName == "String"? "\""+
field.Value + "\"":
field.Value ?? "null");
The LinqDataSource and the Hidden Viewstate - .Net Smoothie
February 21st, 2008 at 10:01 am
[…] a previous post, I mentioned that LINQ-to-SQL updates can be done in two ways: either you make a call to retrieve a […]
Adam Nofsinger
February 26th, 2008 at 12:15 am
Wow, that really sucks. (The fact that you can’t just do something like:
update c in Customer
where c.CustomerID = 12
set c.Name = “Bob WeHadaBabyItsABoy”
DariaK
March 3rd, 2008 at 5:27 am
Here is the tool for database synchronization that supports Linq to Sql object model:
http://perpetuumsoft.com/Product.aspx?lang=en&pid=55
I suppose It is worth to try!
Tomas Jecha
March 8th, 2008 at 3:03 pm
Nice article, thanks.
Dave
March 17th, 2008 at 11:57 pm
I use sprocs for ‘arbitrary updates’, where I’m updating records I haven’t queried and don’t need to query.
Its not ideal, but its simple, can be called through the linq datacontext, doesn’t require a round trip, doesn’t result in SQL strings in code, and doesn’t require a clumsy hack where you are trying to build the object to update it.
Bruno Kenj
May 1st, 2008 at 3:35 am
Pessoa p2 = new Pessoa();
p2.Id = 1;
p2.Nome = “Teste” + DateTime.Now.ToString();
DbContext.Pessoas.Attach(p2);
DbContext.Refresh(RefreshMode.KeepCurrentValues, p2);
DbContext.SubmitChanges();
this way, only chances are updated.
Richard Bushnell
May 1st, 2008 at 6:59 am
@Bruno:
That looks interesting, but doesn’t it really do the same as doing a select before an update?
TUAN
May 6th, 2008 at 1:31 am
I had spend 2 hours to find the way to use Update in LINQ, and now, I want to thanks u about this article
—-
Nice article, thanks u alot!
Neal Walters
May 28th, 2008 at 10:57 pm
How does your changeset.Dump() work?
Thanks,
Neal
Richard Bushnell
May 29th, 2008 at 8:18 am
Hi Neal,
Changeset.Dump() is something that only works in LinqPad.
- The changeset is generated from the DataContext, but in LinqPad, you don’t reference the DataContext, because you are actually inside it.
- Dump() is a LinqPad method defined as an extension method on Object. It is LinqPad specific.
If you want more information about LinqPad, you can contact the authors at www.linqpad.net. When I contacted them, they were very helpful. I’m sure they’d let you know how it works.
HTH,
Richard
Mickey Mouse
June 27th, 2008 at 1:21 am
With NHibernate you just create an instance of the entity, change the fields desired then call the Save method, done!
Christian Zangl
August 1st, 2008 at 10:28 am
If you go to your database.dbml and change the “Update Check” property on the Product fields to “Never” then your code will work and you’ll get the SQL you wanted:
var product = new Product();
product.ProductID = 1;
Products.Attach(product);
product.Name = “Richard’s product”;
SubmitChanges();
Bjarne Pedersen
October 6th, 2008 at 4:25 pm
Well, why not just use:
if(val.recordId == 0){
db.GrandPrixValues.InsertOnSubmit(val);
}
db.SubmitChanges();
Thats working and way more easy!
Sorry Guys » Looking for a better way to update entity
October 9th, 2008 at 12:47 pm
[…] ลองนึกดูว่าถ้ามี Property เยอะ ๆ แล้วจะเกิดอะไรขึ้น หากมีการแก้ไขคงมึนไม่น้อย ลอง Google ดู ก็พบว่ามีคนทำวิธีแบบนี้ด้วย http://richardbushnell.net/index.php/2008/02/18/how-to-update-data-with-linq-to-sql/ […]
anil
October 25th, 2008 at 2:19 pm
Hi I have tested with following code but didn’t worked
Pessoa p2 = new Pessoa();
p2.Id = 1;
p2.Nome = “Teste” + DateTime.Now.ToString();
DbContext.Pessoas.Attach(p2);
DbContext.Refresh(RefreshMode.KeepCurrentValues, p2);
DbContext.SubmitChanges();
this way, only chances are updated.
Can someone let me know whether its possible
Martin Montgomery
October 28th, 2008 at 4:58 pm
Just a quick piece of code to do a general update on all records
private Product UpdateSomething(Product product)
{
product.value = 99;
return product;
}
public void UpdateAll()
{
var list = (from product in DataContext.GetTable()
select UpdateSomething(product)).ToList();
DataContext.SubmitChanges();
}
We can also include a where clause to filter those products being updated
Param Iyer
November 23rd, 2008 at 6:07 am
I could not find a solution to this problem for almost 5 hours before stumbling down here and writing the below piece of code…
Pessoa p2 = new Pessoa();
p2.Id = 1;
p2.Nome = “Teste” + DateTime.Now.ToString();
DbContext.Pessoas.Attach(p2);
DbContext.Refresh(RefreshMode.KeepCurrentValues, p2);
DbContext.SubmitChanges();
this thing does WORK
Damian
January 13th, 2009 at 11:12 pm
Pessoa p2 = new Pessoa();
p2.Id = 1;
p2.Nome = “Teste” + DateTime.Now.ToString();
DbContext.Pessoas.Attach(p2);
DbContext.Refresh(RefreshMode.KeepCurrentValues, p2);
DbContext.SubmitChanges();
It’s works.
Drako
March 31st, 2009 at 2:09 pm
This is my class DBMaintenance - all works fine
public object Insert(object item)
{
using (TransactionScope ts = new TransactionScope())
{
ITable itbl = DataContext.GetTable(item.GetType());
itbl.InsertOnSubmit(item);
itbl.Context.SubmitChanges();
ts.Complete();
}
return item;
}
public object Delete(object item)
{
using (TransactionScope ts = new TransactionScope())
{
ITable itbl = DataContext.GetTable(item.GetType());
itbl.DeleteOnSubmit(item);
itbl.Context.SubmitChanges();
ts.Complete();
}
return item;
}
public object Update(object item)
{
using (TransactionScope ts = new TransactionScope())
{
ITable itbl = DataContext.GetTable(item.GetType());
itbl.Attach(item);
itbl.Context.Refresh(RefreshMode.KeepCurrentValues, item);
itbl.Context.SubmitChanges();
ts.Complete();
}
return item;
}