I have firebase transaction below where it will increment all and year fields inside the stat object. But what if stat doesn't even exist, will firebase automatically create this object for me?
From what I've tested, I got an error saying TypeError: Cannot read property 'all' of null which obviously says stat is null. If so, how can I use transaction to create the object if it doesn't exist before?
const merchantRef = admin.database().ref("statistics/" + merchantId);
merchantRef.transaction(stat => {
stat.all = (parseInt(stat.all) || 0) + price;
stat.year = (parseInt(stat.year) || 0) + price;
return stat;
});
You can do a transaction on a location that doesn't exist. Even if it does exist, you should expect that the first call to your handler function will give you a null, and you will need to return what the data should be at that location.
According to the documentation:
Because your update function is called multiple times, it must be able
to handle null data. Even if there is existing data in your remote
database, it may not be locally cached when the transaction function
is run, resulting in null for the initial value.
You can check whether there is data at the location with by checking if stat is null.
const merchantRef = admin.database().ref("statistics/" + merchantId);
merchantRef.transaction(stat => {
if (!stat) stat = { all: 0, year: 0 };
stat.all += price;
stat.year += price;
return stat;
});
Related
I understand that stored procedures run in the scope of a single partition key.
It is also possible to do operations that change data, not just read it.
ID must be string, so I must roll my own autoincrementer for a separate property to use in documents.
I am trying to make a simple autoincrement number generator that runs in a single stored procedure.
I am partitioning data mimicking a file tree, using forward slashes to separate+concatenate significant bits that make my partition names. Like so:
/sometype/foo/bar/
/sometype/ids/
The first item is always the document type, and every document type will have a 'ids' sub-partition.
Instead of holding documents, the /sometype/ids/ partition will hold and reserve all numerical ids that have been created for this document type, for autoincrement purposes.
this satisfies uniqueness within a partition, stored procedure execution scope, and unique document count within a document type, which is good for my purposes.
I got stumped in a stored procedure where I want to get a specified id, or create it if it does not exist.
I can query my partition with the stored procedure, but the upsert throws an error, using the same partition key.
I designed my database with "pkey" as the name of the property that will holds my partition keys.
Here is the code:
//this stored procedure is always called from a partition of type /<sometype>/ids/ , where <sometype> os one of my document types.
//the /sometype/ids/ is a partition to reserve unique numerical ids, as Cosmos DB does not have a numerical increment out of the box, I am creating a facility for that.
//the actual documents of /sometype/ will be subpartitioned as well for performance.
function getId(opkey, n, id) {
// gets the requested number if available, or next one.
//opkey: string - a partition key of cosmos db of the object that is going to consume the generated ID, if known. must start with /<sometype>/ which is the same that is being used to call this SP
//n: integer - a numerical number for the autoincrement
//id = '' : string - a uuid of the document that is using this id, if known
if (opkey === undefined) throw new Error('opkey cannot be null. must be a string. must be a valid partition key on Cosmos DB.');
n = (n === undefined || n === null)?0:n;
id = (id === undefined || id === null)?'':id;
var collection = getContext().getCollection();
//make opkey parameter into an array
var split_pkey = opkey.split('/');
//recreate the pkey /<sometype>/ids/ because I can't find a reference to this string inside the context.
var idpkey = '/'+split_pkey[1]+'/ids/';
//first query as SQL
//get highest numerical value.
var q = 'SELECT TOP 1 * FROM c \
WHERE c.pkey = \''+idpkey+'\' ORDER BY c.n desc';
//helper function to create uuids. can I ditch it?
function CreateUUID() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Query documents and take 1st item.
var isAccepted = collection.queryDocuments(
collection.getSelfLink(),
q
,
function (firstError, feed, options) {
if (firstError) throw "firstError:"+firstError;
//console.log(collection.options.);
console.log(idpkey+', '+n+', '+id+"-");
var maxn = 0;
// take 1st element from feed
if (!feed || !feed.length) {
//var response = getContext().getResponse();
//response.setBody(null);
}
else {
maxn = feed[0].n;
//var response = getContext().getResponse();
//var body = { original: '', document: '', feed: feed[0] };
//response.setBody(JSON.stringify(body));
}
console.log(maxn);
//query for existing numerical value
q = 'SELECT TOP 1 * FROM c \
WHERE c.pkey = \''+idpkey+'\' \
AND \
c.number = '+n+' \
OR \
c.id = \''+id+'\'';
var isAccepted2 = collection.queryDocuments(
collection.getSelfLink(),
q
,
function (secondFetchError, feed2, options2) {
if (secondFetchError) throw "second error:"+secondFetchError;
//if no numerical value found, create a new (autoincrement)
if (!feed || !feed.length) {
console.log("|"+idpkey);
var uuid = CreateUUID();
var newid = {
id:uuid,
pkey:idpkey,
doc_pkey:opkey,
n:maxn+1
};
//here I used the javascript query api
//it throws an error claiming the primary key is different and I don't know why, I am using idpkey all the time
var isAccepted3 = collection.upsertDocument(
collection.getSelfLink(),
newid
,
function (upsertError,feed3,options3){
if (upsertError) throw "upsert error:"+upsertError;
//if (upsertError) console.log("upsert error:|"+idpkey+"|");
//var response = getContext().getResponse();
//response.setBody(feed[0]);
});
if (!isAccepted3) throw new Error('The third query was not accepted by the server.');
console.log(" - "+uuid);
}
else {
//if id found, return it
//maxn = feed[0].n;
var response = getContext().getResponse();
response.setBody(feed[0]);
//var body = { original: '', document: '', feed: feed[0] };
//response.setBody(JSON.stringify(body));
}
});
if (!isAccepted2) throw new Error('The second query was not accepted by the server.');
});
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
The error message is :
"Requests originating from scripts cannot reference partition keys other than the one for which client request was submitted."
I don't understand why it thinks it is in error, as I am using the variable idpkey in all queries to hold the correct pkey.
Talk about brain fart!
I was violating my own rules because I was misspelling the partition name in the request, making the first part of the partition key /sometype/ different from the parameter sent, causing a mismatch between the execution scope's partition key and the idpkey variable, resulting in the error.
My form contains a field with drop down values (the values came from the choose function) and when I am trying to update the field (with fieldUpdate) in the second time I always get the following error: "A record with the same value already exists in the screen", What is the correct order of actions that need to be done in order to achieve the right behaviour?
This is my attempt to achieve that:
await actions.loginTest();
const form = await actions.priority.formStart(this.formName,
this.onShowMessgeFunc, this.onUpdateFieldsFunc);
_formInstance = form;
const rows = await form.getRows(1);
console.log("parent form rows", rows);
await _formInstance.setActiveRow(1);
form.choose("CUSTNAME", '').then(options => {
let custOptions = options.SearchLine.map(x => {return {label:x.retval + " -
" + x.string1, value: x.retval }});
}).catch((err) => {
console.log("CHOOSE ERROR", err);
})
When I select a value from the drop-down, those are my actions:
await _formInstance.fieldUpdate("CUSTNAME", data.value);
await _formInstance.saveRow(1);
const rows = await _formInstance.getRows(1);
console.log("rows", rows);
In the first time it work's great, but when I select a different value for the second time I get an error that say that this value already exists (it's like it think that I am trying to update the value but I don't, I just want to get the values of other fields that return as a result of the field trigger when I leave the field in Priority). I don't have any purpose to change values, just getting information on other fields and data from sub-forms.
I don't have any purpose to change values, just getting information on other fields and data from sub-forms.
In order to achieve your purpose you could choose one of the following flows, that should work:
Recommended: Use getRows() after you have setSearchFilter() in order to retrieve the row you're interested in including its fields. Then easily setActiveRow() with its index to startSubForm(). You could always use clearRows() to clear the current rows and retrieve others.
Example:
const form = await actions.priority.formStart(this.formName,
this.onShowMessgeFunc, this.onUpdateFieldsFunc);
const filter = {
QueryValues:
[{
field: 'CUSTNAME',
fromval: value,
op: '='
}]
}
await form.setSearchFilter(filter)
const rows = await form.getRows(1);
console.log("parent form rows", rows);
await form.setActiveRow(1);
await form.startSubForm(1);
Perform the 'update' on a newRow() without calling getRows(). Then saveRow() and startSubForm() to get the information you need. Do this for each record you trying to retrieve its data.
Explanation: When calling getRows() you retrieve some rows. Then you cannot update a key field with a value that already exists in the retrieved rows otherwise you get that error.
I'm using context.Database.ExecuteSql to update a table. The update with where clause is executed correctly and the record is updated. However the method returns 2 for rowcount instead of 1. When I execute the update statement in SSMS, the result rowcount returned is 1. Can someone provide insight on this?
string query =
string.Format("update {0} set Description = '{1}', Code = '{2}', LastUpdatedBy = '{3}', LastUpdatedDate = '{4}' where ID = {5};",
tableName,
description,
code,
lastUpdatedBy,
lastUpdatedDate,
ID);
int rowCount = 0;
string message = string.Empty;
using (DBContext context = new DBContext())
{
rowCount = context.Database.ExecuteSqlCommand(TransactionalBehavior.EnsureTransaction, query);
}
return rowCount == 0 ? //this return 2 instead of 1.
new SaveResult(SaveResult.MessageType.Error, string.Format("There was an error updating a record in the {0} table.", tableName), "Index") :
new SaveResult(SaveResult.MessageType.Success, string.Format("The update of {0} was successful.", tableName), "Index");
This returns rowcount = 1 in SSMS:
update zAddressTypes
set Description = 'Current', Code = '101122', LastUpdatedBy = 'user', LastUpdatedDate = '10/20/2014 12:17:26 PM'
where ID = 1;
DECLARE #RowCount INTEGER = ##ROWCOUNT;
select #RowCount;
The rowcount is being returned separately. What you are seeing here is the exit status of the query.
ExecuteSqlCommand return value is for the status of query not for row count. You may want to look into using a datareader or something similar to return the rowcount.
It is the way the datacontext works, see this link:
Entity Framework: Database.ExecuteSqlCommand Method
Apparently the command is updating two records.
Do you have a trigger on your table? I confirmed this behavior can be caused by a trigger as the rows affected by the trigger are added to the row count affected by your SQL command. I commonly check for a 0 or >0 return value to know if anything was affected. You could also return an output variable if you're calling a stored procedure.
var result =
(from bd in context.tblBasicDetails
from pd in context.tblPersonalDetails.Where(x => x.UserId == bd.UserId).DefaultIfEmpty()
from opd in context.tblOtherPersonalDetails.Where(x => x.UserId == bd.UserId).DefaultIfEmpty()
select new clsProfileDate()
{
DOB = pd.DOB
});
foreach (clsProfileDate prod in result)
{
prod.dtDOB = !string.IsNullOrEmpty(prod.DOB) ? Convert.ToDateTime(prod.DOB) : DateTime.Today;
int now = int.Parse(DateTime.Today.ToString("yyyyMMdd"));
int dob = int.Parse(prod.dtDOB.ToString("yyyyMMdd"));
string dif = (now - dob).ToString();
string age = "0";
if (dif.Length > 4)
age = dif.Substring(0, dif.Length - 4);
prod.Age = Convert.ToInt32(age);
}
GetFinalResult(result);
protected void GetFinalResult(IQueryable<clsProfileDate> result)
{
int from;
bool bfrom = Int32.TryParse(ddlAgeFrom.SelectedValue, out from);
int to;
bool bto = Int32.TryParse(ddlAgeTo.SelectedValue, out to);
result = result.AsQueryable().Where(p => p.Age >= from);
}
Here I am getting an exception:
The specified type member "Age" is not supported in LINQ to Entities.
Only initializers, entity members, and entity navigation properties
are supported.
Where Age is not in database it is property I created in clsProfileDate class to calculate Age from DOB. Any solution to this?
You cannot use properties that are not mapped to a database column in a Where expression. You must build the expression based on mapped properties, like:
var date = DateTime.Now.AddYears(-from);
result = result.Where(p => date >= p.DOB);
// you don't need `AsQueryable()` here because result is an `IQueryable` anyway
As a replacement for your not mapped Age property you can extract this expression into a static method like so:
public class clsProfileDate
{
// ...
public DateTime DOB { get; set; } // property mapped to DB table column
public static Expression<Func<clsProfileDate, bool>> IsOlderThan(int age)
{
var date = DateTime.Now.AddYears(-age);
return p => date >= p.DOB;
}
}
And then use it this way:
result = result.Where(clsProfileDate.IsOlderThan(from));
A lot of people are going to say this is a bad answer because it is not best practice but you can also convert it to a List before your where.
result = result.ToList().Where(p => date >= p.DOB);
Slauma's answer is better, but this would work as well. This cost more because ToList() will execute the Query against the database and move the results into memory.
You will also get this error message when you accidentally forget to define a setter for a property.
For example:
public class Building
{
public string Description { get; }
}
var query =
from building in context.Buildings
select new
{
Desc = building.Description
};
int count = query.ToList();
The call to ToList will give the same error message. This one is a very subtle error and very hard to detect.
I forgot to select the column (or set/map the property to a column value):
IQueryable<SampleTable> queryable = from t in dbcontext.SampleTable
where ...
select new DataModel { Name = t.Name };
Calling queryable.OrderBy("Id") will throw exception, even though DataModel has property Id defined.
The correct query is:
IQueryable<SampleTable> queryable = from t in dbcontext.SampleTable
where ...
select new DataModel { Name = t.Name, Id = t.Id };
In my case, I was getting this error message only in Production but not when run locally, even though my application's binaries were identical.
In my application, I'm using a custom DbModelStore so that the runtime-generated EDMX is saved to disk and loaded from disk on startup (instead of regenerating it from scratch) to reduce application startup time - and due to a bug in my code I wasn't invalidating the EDMX file on-disk - so Production was using an older version of the EDMX file from disk that referenced an older version of my application's types from before I renamed the type-name in the exception error message.
Deleting the cache file and restarting the application fixed it.
Advanced answer:
Search in edmx file EntitySetMapping and check if the field is mapped to a column in database:
<EntitySetMapping Name="MY_TABLE">
<EntityTypeMapping TypeName="MYMODEL.MY_TABLE">
<MappingFragment StoreEntitySet="MY_TABLE">
<ScalarProperty Name="MY_COLUMN" ColumnName="MY_COLUMN_NAME" />
</MappingFragment>
</EntityTypeMapping>
</EntitySetMapping>
I was having this problem because the edmx had changes I didn't want and through git I discarded too many changes...
Checking Count() before the WHERE clause solved my problem. It is cheaper than ToList()
if (authUserList != null && _list.Count() > 0)
_list = _list.Where(l => authUserList.Contains(l.CreateUserId));
I tried to fetch some data with the sql.rows() Groovy method and it took a very long time to return the values.
So I tried the "standard" way and it's much much faster (150 times faster).
What am I missing ?
Look at the code below : the first method returns results in about 2500ms and the second in 15 ms !
class MyService {
javax.sql.DataSource dataSource
def SQL_QUERY = "select M_FIRSTNAME as firstname, M_LASTNAME as lastname, M_NATIONALITY as country from CT_PLAYER order by M_ID asc";
def getPlayers1(int offset, int maxRows)
{
def t = System.currentTimeMillis()
def sql = new Sql(dataSource)
def rows = sql.rows(SQL_QUERY, offset, maxRows)
println "time1 : ${System.currentTimeMillis()-t}"
return rows
}
def getPlayers2(int offset, int maxRows)
{
def t = System.currentTimeMillis();
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement();
statement.setMaxRows(offset + maxRows -1);
ResultSet resultSet = statement.executeQuery(SQL_QUERY);
def l_list =[];
if(resultSet.absolute(offset)) {
while (true) {
l_list << [
'firstname':resultSet.getString('firstname'),
'lastname' :resultSet.getString('lastname'),
'country' :resultSet.getString('country')
];
if(!resultSet.next()) break;
}
}
resultSet.close()
statement.close()
connection.close()
println "time2 : ${System.currentTimeMillis()-t}"
return l_list
}
When you call sql.rows, groovy eventually calls SqlGroovyMethods.toRowResult for each row returned by the resultSet.
This method interrogates the ResultSetMetaData for the resultSet each time to find the column names, and then fetches the data for each of these columns from the resultSet into a Map which it adds to the returned List.
In your second example, you directly get the columns required by name (as you know what they are), and avoid having to do this lookup every row.
I think I found the reason this method is so slow : statement.setMaxRows() is never called !
That means that a lot of useless data is sent by the database (when you want to see the first pages of a large datagrid)
I wonder how your tests would turn out if you try with setFetchSize instead of setMaxRows. A lot of this has to the underlying JDBC Driver's default behavior.