send parameter to windows azure mobile server script in c# for Windows 8 Store app - sql

I modified the "Read" operation on my Windows Azure Mobile Services Preview table (named "Item") as follows:
Javascript:
function read(query, user, request)
{
var howRead;
if(howRead == "unique")
{
var sqlUnique = "SELECT DISTINCT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlUnique)
request.execute();
}
else if (howRead == "column")
{
var sqlColumn = "SELECT ? FROM Item WHERE qProjectCode = ?";
mssql.query(sqlColumn)
request.execute();
}
else if (howRead == "all")
{
var sqlAll = "SELECT * FROM Item WHERE qProjectCode = ?";
mssql.query(sqlAll)
request.execute();
}
}
This simply species when I want a unique list of a single column's values returned, all items in a single column, or all columns, respectively, all while limiting the read to those records with a given project code.
Right now, this works in C#, but scans the entire table (with other project codes) and always returns all columns. This is inherently inefficient.
c#
var client = new MobileServiceClient("[https path", "[key]");
var table = client.GetTable<Item>();
var query1 = table.Where(w => w.QProjectCode == qgv.projCode && w.QRecord == (int)lbRecord.Items[uStartRecordIndex]);
var query1Enum = await query1.ToEnumerableAsync();
foreach (var i in query1Enum)
{
// process data
}
How do I alter the c# code to deal with the Javascript code? Feel free to critique the overall approach, since I am not a great programmer and can always use advice!
Thanks

A few things:
In your server code, the mssql calls are not doing anything (useful). If you want to get their results, you need to pass a callback (the call is asynchronous) to it.
Most of your scenarios can be accomplished at the client side. The only for which you'll need server code is the one with the DISTINCT modifier.
For that scenario, you'll need to pass a custom parameter to the server script. You can use the WithParameters method in the MobileServiceTableQuery<T> object to define parameters to pass to the service.
Assuming this data class:
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Other { get; set; }
public string ProjectCode { get; set; }
}
The code below can be used to accomplish the scenarios 2 and 3 at the client side only (no script needed at the server side). The other one will need some script, which I'll cover later.
Task<IEnumerable<string>> ReadingByColumn(IMobileServiceTable<Item> table, string projectCode)
{
return table
.Where(i => i.ProjectCode == projectCode)
.Select(i => i.Name)
.ToEnumerableAsync();
}
Task<IEnumerable<Item>> ReadingAll(IMobileServiceTable<Item> table, string projectCode)
{
return table.Where(i => i.ProjectCode == projectCode).ToEnumerableAsync();
}
Task<IEnumerable<string>> ReadingByColumnUnique(IMobileServiceTable<Item> table, string projectCode)
{
var dict = new Dictionary<string, string>
{
{ "howRead", "unique" },
{ "projectCode", projectCode },
{ "column", "Name" },
};
return table
.Select(i => i.Name)
.WithParameters(dict)
.ToEnumerableAsync();
}
Now, to support the last method (which takes the parameters, we'll need to do this on the server script:
function read(query, user, request)
{
var howRead = request.parameters.howRead;
if (howRead) {
if (howRead === 'unique') {
var column = request.parameters.column; // WARNING: CHECK FOR SQL INJECTION HERE!!! DO NOT USE THIS IN PRODUCTION!!!
var sqlUnique = 'SELECT DISTINCT ' + column + ' FROM Item WHERE ProjectCode = ?';
mssql.query(sqlUnique, [request.parameters.projectCode], {
success: function(distinctColumns) {
var results = distinctColumns.map(function(item) {
var result = [];
result[column] = item; // mapping to the object shape
return result;
});
request.respond(statusCodes.OK, results);
}
});
} else {
request.respond(statusCodes.BAD_REQUEST, {error: 'Script does not support option ' + howRead});
}
} else {
// no server-side action needed
request.execute();
}
}

Related

How to modify the LoadConvert Apex payload?

I've written a trigger under the LeadConvert update event as follows:
trigger WebhookSenderTriggerLeadConvert on Lead (after update) {
if (Trigger.new.size() == 1) {
if (Trigger.old[0].isConverted == false && Trigger.new[0].isConverted == true) {
if (Trigger.new[0].ConvertedAccountId != null) {
String url = 'https://mydomain.io';
String content = WebhookSender.jsonContent(Trigger.new, Trigger.old);
WebhookSender.callout(url, content);
}
}
}
}
This works for me on a dev Salesforce, and in the payload I correctly receive:
{
"new":[
{
"attributes":{
"type":"Lead",
"url":"/services/data/v56.0/sobjects/Lead/B00000000000000000"
},
"Id":"B00000000000000000",
...(+30 more fields)
}
],
"old":[
{
"attributes":{
"type":"Lead",
"url":"/services/data/v56.0/sobjects/Lead/B00000000000000000"
},
"Id":"B00000000000000000",
...(+30 more fields)
}
],
"userId":"A00000000000000000"
}
However in another third party Salesforce account I get the following:
{
"new":[
{
"attributes":{
"type":"Lead",
"url":"/services/data/v56.0/sobjects/Lead/C00000000000000000"
},
...(9 more fields)
}
],
"old":[
{
"attributes":{
},
...(9 more fields)
}
],
"userId":"D00000000000000000"
}
I've obfuscated a lot of the fields here as a lot of it is sensitive, but what i'm unable to determine is what exactly causing a large portion of fields in the third-party Salesforce to not be there, including the Id field, where in the dev Salesforce everything is present.
Is there anything that may be doing this?
EDIT:
Posting WebhookSender, as it's been brought up in comments
public class WebhookSender {
public static String jsonContent(List<Object> triggerNew, List<Object> triggerOld) {
String newObjects = '[]';
if (triggerNew != null) {
newObjects = JSON.serialize(triggerNew);
}
String oldObjects = '[]';
if (triggerOld != null) {
oldObjects = JSON.serialize(triggerOld);
}
String userId = JSON.serialize(UserInfo.getUserId());
String content = '{"new": ' + newObjects + ', "old": ' + oldObjects + ', "userId": ' + userId + '}';
return content;
}
#future(callout=true)
public static void callout(String url, String content) {
Http h = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint(url);
req.setMethod('POST');
req.setHeader('Content-Type', 'application/json');
req.setHeader('Authorization', 'someKey');
req.setBody(content);
if (!Test.isRunningTest()) {h.send(req);}
}
public static Map<String, Object> ParseRequest(RestRequest req) {
String body = req.requestBody.toString();
Map<String, Object> data = (Map<String, Object>) JSON.deserializeUntyped(body);
return data;
}
}
Well, is the WebhookSender class identical in both? Does it dump all fields it received to JSON or does it contain some security-related code such as "stripInaccessible"? Maybe the fields are there but your Profile doesn't see them so strip... cuts them out?
Can it be that your dev org simply has 20+ custom fields in Lead table more than the other org?
Are the fields you're missing coming from managed package? They'd have namespace__FieldName__c format, with 4 underscores total. Maybe the package isn't installed. If you know the fields are there but your user doesn't have license for that managed package - it's possible they'll be hidden.

EF Core decimal precision for Always Encrypted column

Hello I have SQL server with setting up always encrypted feature, also I setup EF for work with always encrypted columns, but when I try to add/update, for Db manipulation I use DbContext, entry in my Db I get follow error:
Operand type clash: decimal(1,0) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '****', column_encryption_key_database_name = '****') is incompatible with decimal(6,2) encrypted with (encryption_type = 'DETERMINISTIC', encryption_algorithm_name = 'AEAD_AES_256_CBC_HMAC_SHA_256', column_encryption_key_name = '*****', column_encryption_key_database_name = '****')
Model that I use
public class Model
{
/// <summary>
/// Payment method name
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Name { get; set; }
/// <summary>
/// Payment method description
/// </summary>
[Column(TypeName = "nvarchar(MAX)")]
public string Description { get; set; }
/// <summary>
/// Fee charges for using payment method
/// </summary>
[Column(TypeName = "decimal(6,2)")]
public decimal Fee { get; set; }
}
Also I tried to specify decimal format in OnModelCreating method
builder.Entity<Model>().Property(x => x.Fee).HasColumnType("decimal(6,2)");
What I missed ?
Thanks for any advice
My colleague and I have found a workaround to the problem using the DiagnosticSource.
You must know that:
Entity Framework Core hooks itself into DiagnosticSource.
DiagnosticSource uses the observer pattern to notify its observers.
The idea is to populate the 'Precision' and 'Scale' fields of the command object (created by EFCore), in this way the call made to Sql will contain all the information necessary to correctly execute the query.
First of all, create the listener:
namespace YOUR_NAMESPACE_HERE
{
public class EfGlobalListener : IObserver<DiagnosticListener>
{
private readonly CommandInterceptor _interceptor = new CommandInterceptor();
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(DiagnosticListener value)
{
if (value.Name == DbLoggerCategory.Name)
{
value.Subscribe(_interceptor);
}
}
}
}
Where CommandInterceptor is:
namespace YOUR_NAMESPACE_HERE
{
public class CommandInterceptor : IObserver<KeyValuePair<string, object>>
{
// This snippet of code is only as example, you could maybe use Reflection to retrieve Field mapping instead of using Dictionary
private Dictionary<string, (byte Precision, byte Scale)> _tableMapping = new Dictionary<string, (byte Precision, byte Scale)>
{
{ "Table1.DecimalField1", (18, 2) },
{ "Table2.DecimalField1", (12, 6) },
{ "Table2.DecimalField2", (10, 4) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
// After that EF Core generates the command to send to the DB, this method will be called
// Cast command object
var command = ((CommandEventData)value.Value).Command;
// command.CommandText -> contains SQL command string
// command.Parameters -> contains all params used in sql command
// ONLY FOR EXAMPLE PURPOSES
// This code may contain errors.
// It was written only as an example.
string table = null;
string[] columns = null;
string[] parameters = null;
var regex = new Regex(#"^INSERT INTO \[(.+)\] \((.*)\)|^VALUES \((.*)\)|UPDATE \[(.*)\] SET (.*)$", RegexOptions.Multiline);
var matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
if(match.Groups[1].Success)
{
// INSERT - TABLE NAME
table = match.Groups[1].Value;
}
if (match.Groups[2].Success)
{
// INSERT - COLS NAMES
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
}
if (match.Groups[3].Success)
{
// INSERT - PARAMS VALUES
parameters = match.Groups[3].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
}
if (match.Groups[4].Success)
{
// UPDATE - TABLE NAME
table = match.Groups[4].Value;
}
if (match.Groups[5].Success)
{
// UPDATE - COLS/PARAMS NAMES/VALUES
var colParams = match.Groups[5].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
}
}
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters
foreach (var item in command.Parameters.OfType<SqlParameter>().Where(p => p.DbType == DbType.Decimal))
{
var index = Array.IndexOf<string>(parameters, item.ParameterName);
var columnName = columns.ElementAt(index);
var key = $"{table}.{columnName}";
// Add Precision and Scale, that fix our problems w/ always encrypted columns
item.Precision = _tableMapping[key].Precision;
item.Scale = _tableMapping[key].Scale;
}
}
}
}
}
Finally add in the Startup.cs the following line of code to register the listener:
DiagnosticListener.AllListeners.Subscribe(new EfGlobalListener());
Ecountered the same issue.
Adjusted #SteeBono interceptor to work with commands which contain multiple statements:
public class AlwaysEncryptedDecimalParameterInterceptor : DbCommandInterceptor, IObserver<KeyValuePair<string, object>>
{
private Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)> _decimalColumnSettings =
new Dictionary<string, (SqlDbType DataType, byte? Precision, byte? Scale)>
{
// MyTableDecimal
{ $"{nameof(MyTableDecimal)}.{nameof(MyTableDecimal.MyDecimalColumn)}", (SqlDbType.Decimal, 18, 6) },
// MyTableMoney
{ $"{nameof(MyTableMoney)}.{nameof(MyTableMoney.MyMoneyColumn)}", (SqlDbType.Money, null, null) },
};
public void OnCompleted()
{
}
public void OnError(Exception error)
{
}
// After that EF Core generates the command to send to the DB, this method will be called
public void OnNext(KeyValuePair<string, object> value)
{
if (value.Key == RelationalEventId.CommandExecuting.Name)
{
System.Data.Common.DbCommand command = ((CommandEventData)value.Value).Command;
Regex regex = new Regex(#"INSERT INTO \[(.+)\] \((.*)\)(\r\n|\r|\n)+VALUES \(([^;]*)\);|UPDATE \[(.*)\] SET (.*)|MERGE \[(.+)\] USING \((\r\n|\r|\n)+VALUES \(([^A]*)\) AS \w* \((.*)\)");
MatchCollection matches = regex.Matches(command.CommandText);
foreach (Match match in matches)
{
(string TableName, string[] Columns, string[] Params) commandComponents = GetCommandComponents(match);
int countOfColumns = commandComponents.Columns.Length;
// After taking all the necessary information from the sql command
// we can add Precision and Scale to all decimal parameters and set type for Money ones
for (int index = 0; index < commandComponents.Params.Length; index++)
{
SqlParameter decimalSqlParameter = command.Parameters.OfType<SqlParameter>()
.FirstOrDefault(p => commandComponents.Params[index] == p.ParameterName);
if (decimalSqlParameter == null)
{
continue;
}
string columnName = commandComponents.Columns.ElementAt(index % countOfColumns);
string settingKey = $"{commandComponents.TableName}.{columnName}";
if (_decimalColumnSettings.ContainsKey(settingKey))
{
(SqlDbType DataType, byte? Precision, byte? Scale) settings = _decimalColumnSettings[settingKey];
decimalSqlParameter.SqlDbType = settings.DataType;
if (settings.Precision.HasValue)
{
decimalSqlParameter.Precision = settings.Precision.Value;
}
if (settings.Scale.HasValue)
{
decimalSqlParameter.Scale = settings.Scale.Value;
}
}
}
}
}
}
private (string TableName, string[] Columns, string[] Params) GetCommandComponents(Match match)
{
string tableName = null;
string[] columns = null;
string[] parameters = null;
// INSERT
if (match.Groups[1].Success)
{
tableName = match.Groups[1].Value;
columns = match.Groups[2].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Replace("[", string.Empty)
.Replace("]", string.Empty)
.Trim()).ToArray();
parameters = match.Groups[4].Value
.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// UPDATE
if (match.Groups[5].Success)
{
tableName = match.Groups[5].Value;
string[] colParams = match.Groups[6].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(p => p.Replace("[", string.Empty).Replace("]", string.Empty).Trim())
.ToArray();
columns = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[0].Trim()).ToArray();
parameters = colParams.Select(cp => cp.Split('=', StringSplitOptions.RemoveEmptyEntries)[1].Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
// MERGE
if (match.Groups[7].Success)
{
tableName = match.Groups[7].Value;
parameters = match.Groups[9].Value.Split(",", StringSplitOptions.RemoveEmptyEntries)
.Select(c => c.Trim()
.Replace($"),{Environment.NewLine}(", string.Empty)
.Replace("(", string.Empty)
.Replace(")", string.Empty))
.ToArray();
columns = match.Groups[10].Value.Split(",", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Replace("[", string.Empty).Replace("]", string.Empty).Trim()).ToArray();
return (
TableName: tableName,
Columns: columns,
Params: parameters);
}
throw new Exception($"{nameof(AlwaysEncryptedDecimalParameterInterceptor)} was not able to parse the command");
}
}

RavenDB querying metadata

I want to prevent documents from being deleted in my project and I decided to use metadata to mark document as Archived. I used below code to do that:
public class DeleteDocumentListener : IDocumentDeleteListener
{
public void BeforeDelete(string key, object entityInstance, RavenJObject metadata)
{
metadata.Add("Archived", true);
throw new NotSupportedException();
}
}
After that I wanted to alter query to return only documents which have Archived metadata value set to false:
using (var session = _store.OpenSession())
{
var query = session.Advanced.DocumentQuery<Cutter>()
.WhereEquals("#metadata.Archived", false);
}
Unfortunately this query return empty result set. It occurs that if Document doesn't have this metadata property then above condition is treated as false. It wasn't what I expected.
How can I compose query to return Documents which don't have metadata property or this property has some value ?
You can solve it by creating an index for you Cutter documents and then query against that:
public class ArchivedIndex : AbstractIndexCreationTask<Cutter>
{
public class QueryModel
{
public bool Archived { get; set; }
}
public ArchivedIndex()
{
Map = documents => from doc in documents
select new QueryModel
{
Archived = MetadataFor(doc)["Archived"] != null && MetadataFor(doc).Value<bool>("Archived")
};
}
}
Then query it like this:
using (var session = documentStore.OpenSession())
{
var cutters = session.Query<ArchivedIndex.QueryModel, ArchivedIndex>()
.Where(x => x.Archived == false)
.OfType<Cutter>()
.ToList();
}
Hope this helps!
Quick side note. To create the index, the following code may need to be run:
new ArchivedIndex().Execute(session.Advanced.DocumentStore);

How to request same parameter twice in query string?

I am trying to request the following query string url: api/item?name=storm&name=prest
I am using the following code below and I cannot get the code to work.
public class ItemController : ApiController
{
private cdwEntities db = new cdwEntities();
public HttpResponseMessage Get([FromUri] Query query)
{
var data = db.database_ICs.AsQueryable();
if (query.name != null)
{
**data = data.Where(c => c.Name.Split("&").Contains(query.name));**
}
if (query.id!= null)
{
data = data.Where(c => c.ID== query.id);
}
if (!data.Any())
{
var message = string.Format("No data was found");
return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
}
return Request.CreateResponse(HttpStatusCode.OK, data);
}
}
Any help would be very much appreciated.
You can use post Api and send array of [name].
name = [item1,item2....]
public void Post([FromBody] List<string> name) {
}
You can not pass same name key in Querystring. Browser/Code did not identified which is correct value, if you want multiple value then pass as a object.

EntityFramework, Insert if not exist, otherwise update

I'm having a Entity-Set Countries, reflecting a database table '<'char(2),char(3),nvarchar(50> in my database.
Im having a parser that returns a Country[] array of parsed countries, and is having issues with getting it updated in the right way. What i want is: Take the array of countries, for those countries not already in the database insert them, and those existing update if any fields is different. How can this be done?
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
//Code missing
}
}
I was thinking something like
for(var c in data.Except(db.Countries)) but it wount work as it compares on wronge fields.
Hope anyone have had this issues before, and have a solution for me. If i cant use the Country object and insert/update an array of them easy, i dont see much benefict of using the framework, as from performers i think its faster to write a custom sql script that inserts them instead of ect checking if an country is already in the database before inserting?
Solution
See answer of post instead.
I added override equals to my country class:
public partial class Country
{
public override bool Equals(object obj)
{
if (obj is Country)
{
var country = obj as Country;
return this.CountryTreeLetter.Equals(country.CountryTreeLetter);
}
return false;
}
public override int GetHashCode()
{
int hash = 13;
hash = hash * 7 + (int)CountryTreeLetter[0];
hash = hash * 7 + (int)CountryTreeLetter[1];
hash = hash * 7 + (int)CountryTreeLetter[2];
return hash;
}
}
and then did:
var data = e.ParsedData as Country[];
using (var db = new entities())
{
foreach (var item in data.Except(db.Countries))
{
db.AddToCountries(item);
}
db.SaveChanges();
}
I would do it straightforward:
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
foreach(var country in data)
{
var countryInDb = db.Countries
.Where(c => c.Name == country.Name) // or whatever your key is
.SingleOrDefault();
if (countryInDb != null)
db.Countries.ApplyCurrentValues(country);
else
db.Countries.AddObject(country);
}
db.SaveChanges();
}
}
I don't know how often your application must run this or how many countries your world has. But I have the feeling that this is nothing where you must think about sophisticated performance optimizations.
Edit
Alternative approach which would issue only one query:
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
var names = data.Select(c => c.Name);
var countriesInDb = db.Countries
.Where(c => names.Contains(c.Name))
.ToList(); // single DB query
foreach(var country in data)
{
var countryInDb = countriesInDb
.SingleOrDefault(c => c.Name == country.Name); // runs in memory
if (countryInDb != null)
db.Countries.ApplyCurrentValues(country);
else
db.Countries.AddObject(country);
}
db.SaveChanges();
}
}
The modern form, using later EF versions would be:
context.Entry(record).State = (AlreadyExists ? EntityState.Modified : EntityState.Added);
context.SaveChanges();
AlreadyExists can come from checking the key or by querying the database to see whether the item already exists there.
You can implement your own IEqualityComparer<Country> and pass that to the Except() method. Assuming your Country object has Id and Name properties, one example of that implementation could look like this:
public class CountryComparer : IEqualityComparer<Country>
{
public bool Equals(Country x, Country y)
{
return x.Name.Equals(y.Name) && (x.Id == y.Id);
}
public int GetHashCode(Country obj)
{
return string.Format("{0}{1}", obj.Id, obj.Name).GetHashCode();
}
}
and use it as
data.Countries.Except<Country>(db, new CountryComparer());
Although, in your case it looks like you just need to extract new objects, you can use var newCountries = data.Where(c => c.Id == Guid.Empty); if your Id is Guid.
The best way is to inspect the Country.EntityState property and take actions from there regarding on value (Detached, Modified, Added, etc.)
You need to provide more information on what your data collection contains i.e. are the Country objects retrieved from a database through the entityframework, in which case their context can be tracked, or are you generating them using some other way.
I am not sure this will be the best solution but I think you have to get all countries from DB then check it with your parsed data
void Method(object sender, DocumentLoadedEvent e)
{
var data = e.ParsedData as Country[];
using(var db = new DataContractEntities)
{
List<Country> mycountries = db.Countries.ToList();
foreach(var PC in data)
{
if(mycountries.Any( C => C.Name==PC.Name ))
{
var country = mycountries.Any( C => C.Name==PC.Name );
//Update it here
}
else
{
var newcountry = Country.CreateCountry(PC.Name);//you must provide all required parameters
newcountry.Name = PC.Name;
db.AddToCountries(newcountry)
}
}
db.SaveChanges();
}
}