Is there any way in table storage to read and then update a record? For example in SQL server I would use a query like this:
UPDATE table
SET
testValue = 1
OUTPUT
inserted.columnA,
inserted.columnB,
inserted.columnC
WHERE
testValue = 0
Currently my code looks like this:
var filter = "testValue eq 0";
var rangeQuery = new TableQuery<AzStorageEntityAdapter<T>>().Where(filter);
var result = _cloudTable.ExecuteQuery(rangeQuery);
var azStorageEntities = result.ToList();
IList<T> results = azStorageEntities.Select(r => r.InnerObject).ToList();
Is there some way to add a update clause along with my where clause when it reads the values that meet the filters criteria that 'testValue' is also updated to 1?
Unfortunately it is not possible in a single operation.
You must first fetch an entity (1st operation), update it and then save it back in the table (2nd operation).
Related
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.
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
I understand this method of getting DB data (using foreach):
var db = Database.Open("Connection");
var rows = db.Query("SELECT 1 columnName,2 columnName2 FROM Table");
foreach(var row in rows){
var data = row.columnName;
//or
var data = row[0];
}
This works, but how do I get the data without using a foreach?
var data = rows[0][1];
^ This doesn't work.
Basically, I am trying to figure out how to get the data without using a foreach. What would I have to do?
EDIT:
.Query() returns:
Type: System.Collections.Generic.IEnumerable
The rows returned by the SQL query.
As shown here: http://msdn.microsoft.com/en-us/library/webmatrix.data.database.query%28v=vs.111%29.aspx
You can use the ElementAt method to get to the row then use indexing to get to the column of that row:
var data = rows.ElementAt(0)[0];
You can even reference the columns by name as you are actually receiving an IEnumerable<dynamic>:
var row = rows.ElementAt(0);
//now you can access:
//row.columnName
//row.columnName2
Using ToArray() on your collection is an option;
var results = db.Query("SELECT * FROM Table").ToArray();
Console.WriteLine(results[1][2]);
This will allow you to reference your result set the way you want. However, if you don't bound your collection in the query somehow, you could end up loading a large collection into memory to reference it this way. I've never seen the WebMatrix Database before, so I can't give any tips on doing this in the most efficient manner, but ToArray() will give you what you want.
db.Query actually returns type DynamicRecord, so if you want to reference your column names by name, you can do;
var results = db.Query("SELECT * FROM Table").Cast<DynamicRecord>().ToArray();
Console.WriteLine(results[0]["Id"]);
Now you can use column names
And as petelids mentions,
IEnumerable<dynamic> results = db.Query("SELECT * FROM Table");
Console.WriteLine(results.ElementAt(0).Id);
If you are wanting a specific answer for the WebMatrix.Data example, then this won't help you at all.
However, if you like writing actual SQL statements, but don't like tedious mapping code, then I like to use micro-ORMs like OrmLite, PetaPoco, Massive, Dapper, etc...
This is an example using my favorite: http://www.toptensoftware.com/petapoco/
http://www.nuget.org/packages/petapoco
/// <summary>
/// Create a Poco mapping class where the class name matches the Table name
/// and the properties match the column names and data types from the table
/// </summary>
public class Table{
public int ColumnName {get;set;}
public int ColumnName2 {get;set;}
}
int id = 1;
var db = new Database("Connection Name");
const string sql = #"SELECT
1 columnName,
2 columnName2
FROM Table
WHERE ColumnName = #0";
return db.FirstOrDefault<Table>(sql, id);
or
// Using auto-generated Select statement
return db.FirstOrDefault<Table>("WHERE ColumnName = #0", id);
// Fetch all records...
return db.Fetch<Table>("");
// PetaPoco also supports dynamic
return db.FirstOrDefault<dynamic>(sql, id);
What does db.Query returns is some information we need.
What you can try is fill your result in a datatable.
And then getting it back like this:
DataTable dt = db.Query("SELECT 1 columnName,2 columnName2 FROM Table");
string s= dt.Rows[0][1].ToString();
But to be honest as long as you can get 0 or more then 1 results back you want to use a loop to irate through.
If you are using DataTable use:
datatable.rows[0][1].toString()
or if you are using an IEnumerable object use
objectName[0].propertyName.toString()
Here the propertyName is the name of the property you are using for that DataColumn.
From http://msdn.microsoft.com/en-us/library/webmatrix.data.database.query%28v=vs.111%29.aspx i am seeing query method returns IEnumerable objects so you may need the second way.
Thank you
I'm new to writing LINQ queries, and I'm having trouble with string comparisons. I'm loading the data into a DataTable (confirmed that table in SQL DB and DataTable have same number of rows), but for some reason I can't find a value that I know exists in both.
The text box contains 'servername' while the datarows contain 'servername.mydomain.net', so here's what my code looks like
string strParameter = txtAutoComplete.ToString().ToLower();
//WUG TableAdapter and DataTable
dsCIInfoTableAdapters.DeviceTableAdapter taWUG;
taWUG = new dsCIInfoTableAdapters.DeviceTableAdapter();
dsCIInfo.DeviceDataTable dtWUG = new dsCIInfo.DeviceDataTable();
taWUG.Fill(dtWUG);
var qstWUG = (from row in dtWUG.AsEnumerable()
where row.Field<string>("sDisplayName").ToLower().Contains(strParameter)
select row.Field<string>("sDisplayName"));
Beleive in your LINQ statement dtWUG needs to be dtWUG.AsEnumerable(). Linq only works on data sources that implement the IEnumerable Interface.
You can debug it easier if you add some let statements where you can add breakpoints:
var qstWUG = (from row in dtWUG
let display = row.Field<string>("sDisplayName")
let lower = display.ToLower()
let contains = lower.Contains(strParameter)
where contains
select display).ToArray();
Also convert it to an array using .ToArray() at the end, will make it execute immediately (LINQ is lazy by paradigm, doesn't execute until it's needed), and also easier to look at in subsequent breakpoints.
Yeah, I feel stupid... I forgot to use the textbox.text to assign it to a string
string strParameter = txtAutoComplete.Text.ToLower();
//WUG TableAdapter and DataTable
dsCIInfoTableAdapters.DeviceTableAdapter taWUG;
taWUG = new dsCIInfoTableAdapters.DeviceTableAdapter();
dsCIInfo.DeviceDataTable dtWUG = new dsCIInfo.DeviceDataTable();
taWUG.Fill(dtWUG);
var qstWUG = (from row in dtWUG.AsEnumerable()
let display = row.Field<string>("sDisplayName")
where display.ToLower().Contains(strParameter)
select display).ToArray();
I previously asked the question and got answer to Best approach to write query but the problem is that if you have to save this result in a list then there duplication of records. For example
the resultant table of the join given EXAMPLE
See there are duplicate rows. How can you filter them out, and yet save the data of order number?
Of course there may be some ways but I am looking for some great ways
How can we store the data in list and not create duplicate rows in list?
My current code for my tables is
int lastUserId = 0;
sql_cmd = new SqlCommand();
sql_cmd.Connection = sql_con;
sql_cmd.CommandText = "SELECT * FROM AccountsUsers LEFT JOIN Accounts ON AccountsUsers.Id = Accounts.userId ORDER BY AccountsUsers.accFirstName";
SqlDataReader reader = sql_cmd.ExecuteReader();
if (reader.HasRows == true)
{
Users userToAdd = new Users();
while (reader.Read())
{
userToAdd = new Users();
userToAdd.userId = int.Parse(reader["Id"].ToString());
userToAdd.firstName = reader["accFirstName"].ToString();
userToAdd.lastName = reader["accLastName"].ToString();
lastUserId = userToAdd.userId;
Websites domainData = new Websites();
domainData.domainName = reader["accDomainName"].ToString();
domainData.userName = reader["accUserName"].ToString();
domainData.password = reader["accPass"].ToString();
domainData.URL = reader["accDomain"].ToString();
userToAdd.DomainData.Add(domainData);
allUsers.Add(userToAdd);
}
}
For second table I have custom list that will hold the entries of all the data in second table.
The table returned is table having joins and have multiple rows for same
Besides using the Dictionary idea as answered by Antonio Bakula...
If you persist the dictionary of users and call the code in your sample multiple times you should consider that a user account is either new, modifed, or deleted.
The algorithm to use is the following when executing your SQL query:
If row in query result is not in dictionary create and add new user to the dictionary.
If row in query result is in dictionary update the user information.
If dictionary item not in query result delete the user from the dictionary.
I'd also recommend not using SELECT *
Use only the table columns your code needs, this improves the performance of your code, and prevents a potential security breach by returning private user information.
i am not sure why are you not using distinct clause in your sql to fetch unique results. also that will be faster. did you look at using hashtables.
I would put users into Dictonary and check if allready exists, something like this :
Dictionary<int, Users> allUsers = new Dictionary<int, Users>()
and then in Reader while loop :
int userId = int.Parse(reader["Id"].ToString());
Users currUser = allUsers[userId];
if (currUser == null)
{
currUser = new Users();
currUser.userId = userId);
currUser.firstName = reader["accFirstName"].ToString();
currUser.lastName = reader["accLastName"].ToString();
allUsers.Add(userID, currUser);
}
Websites domainData = new Websites();
domainData.domainName = reader["accDomainName"].ToString();
domainData.userName = reader["accUserName"].ToString();
domainData.password = reader["accPass"].ToString();
domainData.URL = reader["accDomain"].ToString();
currUser.DomainData.Add(domainData);
Seems like the root of your problem is in your database table.
When you said duplicate data rows, are you saying you get duplicate entries in the list or you have duplicate data in your table?
Give 2 rows that are duplicate.
Two options:
First, prevent pulling duplicate data from sql by using a distinct clause like:
select distinct from where
Second option as mentioned Antonio, is to check if the list already has it.
First option is recommended unless there are other reasons.