LINQ to Entities how to update a record - c#

Okay, so I'm new to both EF and LINQ. I have figured out how to INSERT and DELETE but for some reason UPDATE seems to escape my grasp.
Here is a sample of my code:
EntityDB dataBase = new EntityDB();
Customer c = new Customer
{
Name = "Test",
Gender = "Male
};
dataBase.Customers.AddObject(c);
dataBase.SaveChanges();
The above creates and adds a record just fine.
Customer c = (from x in dataBase.Customers
where x.Name == "Test"
selext x).First();
dataBase.Customers.DeleteObject(c);
dataBase.SaveChanges();
The above effectively deletes the specified record.
Now how do I update? I can't seem to find an "UpdateObject()" method on the entity collection.

Just modify one of the returned entities:
Customer c = (from x in dataBase.Customers
where x.Name == "Test"
select x).First();
c.Name = "New Name";
dataBase.SaveChanges();
Note, you can only update an entity (something that extends EntityObject, not something that you have projected using something like select new CustomObject{Name = x.Name}

//for update
(from x in dataBase.Customers
where x.Name == "Test"
select x).ToList().ForEach(xx => xx.Name="New Name");
//for delete
dataBase.Customers.RemoveAll(x=>x.Name=="Name");

They both track your changes to the collection, just call the SaveChanges() method that should update the DB.

In most cases #tster's answer will suffice. However, I had a scenario where I wanted to update a row without first retrieving it.
My situation is this: I've got a table where I want to "lock" a row so that only a single user at a time will be able to edit it in my app. I'm achieving this by saying
update items set status = 'in use', lastuser = #lastuser, lastupdate = #updatetime where ID = #rowtolock and #status = 'free'
The reason being, if I were to simply retrieve the row by ID, change the properties and then save, I could end up with two people accessing the same row simultaneously. This way, I simply send and update claiming this row as mine, then I try to retrieve the row which has the same properties I just updated with. If that row exists, great. If, for some reason it doesn't (someone else's "lock" command got there first), I simply return FALSE from my method.
I do this by using context.Database.ExecuteSqlCommand which accepts a string command and an array of parameters.
Just wanted to add this answer to point out that there will be scenarios in which retrieving a row, updating it, and saving it back to the DB won't suffice and that there are ways of running a straight update statement when necessary.

Related

Selecting multiple columns for updating in Linq

I have a products table which contains thousands of products. I want to update only two columns (price, isAvailable) of this table. So is there is a way to select only those two columns from this table?
This is the code that I am using. But I don't want to select all columns.
var dbModels = await DbContext.Products
.Where(x => x.SellerId == sellerId)
.ToListAsync();
I have tried this
var db = await DbContext.ProductSkuDetail
.Where(x => x.SellerId == sellerId)
.Select(y => new
{
Price = y.Price,
IsAvailable = y.IsAvailable
}).ToListAsync();
But this is read-only. I want to update those columns.
You must include the primary key in the anonymous type:
var models = await context.Products
.Where(p => p.SellerId == sellerId)
.Select(p => new
{
Id = p.Id, // primary key
Price = p.Price,
IsAvailable = p.IsAvailable
})
.ToListAsync();
Then, when you need to save the data back to the database, you need to create entities with the same primary key and attach them to the context.
foreach (var x in models)
{
var product = new Product
{
Id = x.Id,
Price = newPrice, // get new price somehow
IsAvailable = false // get new availability somehow
};
context.Attach(product);
var entry = context.Entry(product);
entry.Property("Price").IsModified = true;
entry.Property("IsAvailable").IsModified = true;
}
await context.SaveChangesAsync();
More info about Attach
Yes, there is a way to specify exactly which columns you want.
No, you can't use that method to update data.
When fetching data using entity framework DbSet<...>, there are two methods: fetch the complete row of the table, or only fetch certain properties of the row.
The first method is used, if you execute the query without using Select. If you do this, the data is copied to the DbContext.ChangeTracker.
Other methods like DbSet.Find and IQueryble.Include will also copy the fetched data to the ChangeTracker.
If you use Select to specify the data that you want to fetch, then the fetched data will not be copied into the ChangeTracker.
When you call DbContext.SaveChanges, the ChangeTracker is used to determine what items are changed or removed and thus need to be updated.
The ChangeTracker keeps the original fetched data, and a copy of it. You get the reference to the copy as the result of your changes. So whenever you change the values of properties of your reference to the copy, they are changed in the copy that is in the ChangeTracker.
When you call SaveChanges, the copy is compared to the original in the ChangeTracker, to detect which properties are changed.
To improve efficiency, if you don't plan to update the fetched data, it is wise to make sure that the fetched data is not in the ChangeTracker.
When using entity framework to fetch data, always use Select and fetch only the properties that you actually plan to use. Only query without Select if you plan to change the fetched data.
Change = update properties, or remove the complete row. Also: only use Find and Include if you plan to update the fetched data.
You want to update the fetched row
Hence you have to fetch the complete row: don't use Select, fetch the complete row.
If you want to fetch the item by primary key, consider to use DbSet.Find. This has the small optimization that if it is already in the ChangeTracker, then the data won't be fetched again.
Consider to write SQL for this
Usually you don't have to update thousands of items on a regular basis. However, if you have to do this often, consider to update using sql:
using (var dbContext = new MyDbContext(...))
{
const string sqlText = #"Update products
SET Price = #Price, IsAvailable = #IsAvailable....
Where SellerId = #SellerId;";
var parameters = new object[]
{
new SqlParameter("#SellerId", sellerId),
new SqlParameter("#Price", newPrice),
new SqlParameter("#IsAvailable", newAvailability),
};
dbContext.DataBase.ExecuteSqlCommand(sqlText, parameters);
}
(You'll have to check the validity of the SQL command in my example. Since I use entity framework, my SQL is a bit rusty.)
By the way: although this method is very efficient, you'll lose the advantages of entity framework: the decoupling of the actual database from the table structure: the names of your tables and columns seep through until this statement.
My advice would be only to use direct SQL for efficiency: if you have to update quite often. Your DbContext hides the internal layout of your database, so make this method part of your DbContext
public void UpdatePrice(int sellerId, bool IsAvailable, decimal newPrice)
{
const string sqlText = ...
var params = ...
this.Database.ExecuteSqlCommand(sqlText, params);
}
Alas, you'll have to call this once per update, there is no SQL command that will update thousands of items with different prices in one SQLcommand
}
You can use the ExecuteSqlCommandAsync Command to write the query which will be executed in your SQL Server.
await dbContext
.Database
.ExecuteSqlCommandAsync("UPDATE Products SET Price = {0}, IsAvailable = {1} WHERE SelleriId = {2}", new object[] { priceValue, isAvailableValue, sellerId });
EF doesn't fit well for batch operations.
It works with objects, not tables, records or fields. If you want to update or delete object(s), you need to read it(them) first. This allows EF change tracker to track changes in field values or the whole object state, and generate appropriate SQL.
For batch operations consider using raw SQL queries, light-weight libraries like Dapper, or third-party packages like Entity Framework Plus.

how to get max id from a table using linq

I have a table Estimation which has an column EstimationNo,i am trying to get the max EstimationNo like this-
var result = cont.SalesEstimateCont.Where(x => x.Org_ID == CurrentOrgId);
var estimationMaxNo = result.Any() ? result.Max(x => x.EstimateNo) + 1 : 1;
var DigitalEstimate = new SalesEstimate()
{
EstimateNo=estimationMaxNo;
};
cont.Estimate.Add(DigitalEstimate );
cont.Savechanges();
but the problem is, if same table is saving by different users at same time its saving the same EstimationNo for both users. like- 10,10
Now, how to handle this issue..please give some solution.
Best strategy is to let db engine (I assume that it is SQL Server) handle incrementing of EstimateNo field. This can be done with identity specification which can be added to normal not primary key field also.
ALTER TABLE SalesEstimateCont drop column EstimateNo
go
ALTER TABLE SalesEstimateContadd Add EstimateNo int NOT NULL IDENTITY (1,1)
Please note: if you have existing data or some data should be modified, you may need some extra effort to achieve this (i.e with temp tables and by setting IDENTITY INSERT ON)
I got a simple answer.I just had to use transacationScope class.
and lock the resource table. like this-
using (TransactionScope scope = new TransactionScope())
{
cont.Database.ExecuteSqlCommand("SELECT TOP 1 * FROM Sales__Estimate WITH (TABLOCKX, HOLDLOCK)");
var result = cont.SalesEstimateCont.Where(x => x.Org_ID == CurrentOrgId);
var estimationMaxNo = result.Any() ? result.Max(x => x.EstimateNo) + 1 : 1;
var DigitalEstimate = new SalesEstimate()
{
EstimateNo=estimationMaxNo;
};
cont.Estimate.Add(DigitalEstimate );
cont.Savechanges();
}
If you can make EstimateNo an Identity column, that is the easiest/best way to fix this. If you can change this to a Guid, that would be another easy way to fix this as PK would be unique regardless of the user.
If you can't do either of these and you must take Max() manually, you might want to consider creating another table that stores the next available number there. Then you can create a new SqlCommnand with a Serializable transaction to lock the table, update the # by 1 and select it back. If two update commands hit at the same time, only one update will run and won't let go until that connection with Serializable transaction gets closed. This allows you to select the newly updated number before the other update runs and get the now "unique" next number.
You can OrderByDescending and then Take the the first record
var estimationMaxNo = result.OrderByDescending(x => x.EstimateNo).Take(1);
It can be done in a single command. You need to set the IDENTITY property for primary id
ALTER TABLE SalesEstimateCont ADD Org_ID int NOT NULL IDENTITY (1,1) PRIMARY KEY

Get present primary keys from link table, set Checkstate checklistbox

I have a CheckedListbox which contains values from some table called products.
The idea is to check the products that are associated to a customer. Now it does save correctly in an link table, yet when loading it again, the items that were checked do not get loaded correctly into the CheckedListbox.
So from that link table where, I would like to get all rows from just one column. All tables are already loaded into the application so I don't want to use sql.
I've tried using linq, with no success, Ids is just empty here.
int[] Ids = (from m in dataset.Tables["LinkTable"].AsEnumerable()
where m.Field<int>("customerId") == customerId
select m.Field<int>("productId")).ToArray();
Then, if I do succeed to get those Id's, I would like to get the indexes of those primary keys so I can set the correct products to checked.
I've tired doing it like this, but this gives me error in other parts of the program, because I am setting a Primary key to a global datatable. Datagridviews don't like that.
DataColumn[] keyColumns = new DataColumn[1];
keyColumns[0] = dataset.Tables["products"].Columns["Id"];
currentPatient.GetTheDataSet.Tables["products"].PrimaryKey = keyColumns;
foreach (int Id in Ids)
{
DataRow row = dataset.Tables["Products"].Rows.Find(Id);
int index = dataset.Tables["Products"].Rows.IndexOf(row);
clbMedications.SetItemChecked(index, true);
}
I would like to do that last part without specifying a primary key, I couldn't find how to do that in linq.
I know it consists of 2 questions, but perhaps this can be done with just one linq statement so I better combine them.
[EDIT]
Finally, i think i've got what you need:
var qry = (from p in ds.Tables["products"].AsEnumerable()
select new {
Id = p.Field<int>("Id"),
Index = ds.Tables["products"].Rows.IndexOf(p),
Checked = ds.Tables["LinkTable"].AsEnumerable().Any(x=>x.Field<int>("productId") == p.Field<int>("Id") && x.Field<int>("customerId")==customerid)
}).ToList();
Above query returns the list, which you can bnid with CheckedListbox.

Autonumber issue, NewRow returns different ID then what it gets saved as in database once committed. (C# dataset)

RejectChanges doesn't rollback the autonumber (I understand why thanks to this post). But, when I save a row. It overrides the autonumber which was originally returned to me. I need to know the ID of the parent row so I can create the related records.
Perhaps there is a better way I should be creating the related rows?
I saw this post which recommended changing the AutoIncrement to -1 and AutoSeed to -1. But, that does't make sense nor did it work for me.
In my code there are two tables, Shipments and Shipment_Package_Details. It's a 1-many relationship.
r = this.allertDataSet3.Shipments.NewShipmentsRow();
log.Debug("The new ShipmentRow ID is " + r.ShipmentID); // ID == 1
this.allertDataSet3.Shipments.RejectChanges();
r = this.allertDataSet3.Shipments.NewShipmentsRow();
log.Debug("The new ShipmentRow ID is " + r.ShipmentID); // ID == 2
r.NumPkgs = p.Length;
this.allertDataSet3.Shipments.Rows.Add(r);
// Create Related Row
MyCompany.MyNamespace.allertDataSet3.Shipment_Package_DetailsRow pd = this.allertDataSet3.Shipment_Package_Details.NewShipment_Package_DetailsRow();
pd.ShipmentID = r.ShipmentID; // ID == 2 here.
pd.TrackingNumber = trackingNumbers[i];
this.allertDataSet3.Shipment_Package_Details.Rows.Add(pd);
this.shipmentsTableAdapter.Update(this.allertDataSet3);
// At this point if i set a breakpoint and look at database, it's been committed to the database with ID == 1.
// The following line will fail since I set the package details row to ID == 2.
this.shipment_Package_DetailsTableAdapter.Update(this.allertDataSet3);

SubmitChanges Inserts new record for foreign relation instead of use the assigned one

I got the following Tables with valid relations as shown below:
Report
------>ReprotDataSource
--------->SharePointDomain
Now, when i try the following ( link the newly ReprotDataSource to the Selected SharePointDomain) it insertes a new SharePointDomain Record instead of refrence it to the SharePointDomain with id (2)
//Create new Object
ReportDataSource rprtDS = new ReportDataSource
{
Name = rprtDSSelected.Name,
Parent = rprtDSSelected.Parent,
CreatedBy = Environment.UserName,
CreationDate = DateTime.Now,
Source = rprtDSSelected.Source,
Type = rprtDSSelected.Type
};
if (rprtDS.Type == "SP List")
//here is the issue
rprtDS.SharePointDomain = selectedSharePointDomain;//its id = 2
//Add to EntitySet
TheReport.ReportDataSources.Add(rprtDS);
TheReport.Save();
It works fine when i set the id my self to (2)
any explanations.?
Thank you in advance.
The object you are adding must come from the same data-context, otherwise it will count as an implicit insert. I'm guessing this object has come from elsewhere; a previous data-context perhaps. This is tricky if you are caching the object between queries. Maybe just set the id instead... :p
You might have some joy detaching and attaching as necessary, but it probably isn't worth it.

Categories