Bug in OrmLite - updating record with Primary Key = 0 - servicestack

Given a simple poco
public class Model
{
[PrimaryKey]
public int ID { get; set; }
public string Description { get; set; }
}
this works fine ...
var connectionString = #"Data Source=WIN8PC\SQLEXPRESS;Initial Catalog=Test;Integrated Security=True;";
connectionFactory = new OrmLiteConnectionFactory(connectionString, SqlServerDialect.Provider);
using (var db = connectionFactory.OpenDbConnection())
{ db.DropAndCreateTable<Model>(); }
var model0 = new Model { ID = 0, Description = "Item Zero" };
var model1 = new Model { ID = 1, Description = "Item One" };
using (var db = connectionFactory.OpenDbConnection())
{ db.Save(model0, model1); }
as does this ...
model0.Description += " updated";
model1.Description += " updated";
using (var db = connectionFactory.OpenDbConnection())
{
db.Save(model0);
db.Save(model1);
}
however, this crashes with a primary key violation exception ...
model0.Description += " updated again";
model1.Description += " updated again";
using (var db = connectionFactory.OpenDbConnection())
{ db.Save(model0, model1); }
The record with ID zero is required, as this is a lookup table to replace an existing C# enum type. This is a local copy of distributed data (that I don't control), so there's no reason to have an auto-increment key.
The issue appears to be in OrmLiteWriteCommandExtensions.SaveAll() - any row with id == defaultValue is assumed to be a new item, rather than an update of an existing record. The same issue occurs in the parallel async methods too.
Is there any other way to get around this issue, other than by saving each record individually (inside a transaction). It would be preferable to save all updated records for a table in one command.

Save is a high-level API that will INSERT or UPDATE based on whether or not the Primary Key has a value. If you want to insert a default Primary Key value you can use Insert instead as seen in this Live Example on Gistlyn:
public class Model
{
[PrimaryKey]
public int ID { get; set; }
public string Description { get; set; }
}
db.DropAndCreateTable<Model>();
var model0 = new Model { ID = 0, Description = "Item Zero" };
var model1 = new Model { ID = 1, Description = "Item One" };
db.Insert(model0, model1);
var rows = db.Select<Model>();
"Inserted Rows: {0}".Print(rows.Dump());
Which outputs:
Inserted Rows: [
{
ID: 0,
Description: Item Zero
},
{
ID: 1,
Description: Item One
}
]

Related

Why doesn't my BLC 'save' action save all records unless its at the very end of the cache insert?

I have BLC code that is parsing out the AuditHistory 'ModifiedFields' field so that I have multiple lines with separate 'Field' and 'Value' fields. The 'ModifiedFields' field contains null separated values, so my BLC C# code uses the split function to put these into an array. This all works fine. The problem I'm having is saving to a table in Acumatica via the Graph / Cache 'insert' function. If I use the 'Actions.PressSave()' method after every iteration of the array, it doesn't save every record - effectively skipping records. I have no idea why this would happen. If I put the 'Actions.PressSave()' method at the very end of everything, I get all the records - but sometimes it times out, I'm assuming because of (in some cases) the massive amount of records being cached before the save.
Putting the PressSave method at ANY other point in the loop(s) results in missed records.
Here is my BLC code (note the several places I placed the PressSave method for testing, but commented out - leaving the last one):
public PXAction<AUAuditSetup> CreateAuditRecords;
[PXProcessButton]
[PXUIField(DisplayName = "Create Audit Records", MapEnableRights = PXCacheRights.Update, MapViewRights = PXCacheRights.Update)]
protected virtual IEnumerable createAuditRecords(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate ()
{
//Create graph of TAC screen...
var osdm = PXGraph.CreateInstance<OpenSourceDataMaint>();
xTACOpenSourceDetail osd;
int recordID = 1;
//Get records from AuditHistory
//PXResultset<AuditHistory> res = PXSelect<AuditHistory>.Select(Base);
PXResultset<AuditHistory> res = PXSelect<AuditHistory,
Where<AuditHistory.changeDate, GreaterEqual<Required<AuditHistory.changeDate>>>>.Select(Base, Convert.ToDateTime("01/01/2013"));
var companyID = Convert.ToString(PX.Common.PXContext.GetSlot<int?>("singleCompanyID"));
foreach (PXResult<AuditHistory> rec in res)
{
var ah = (AuditHistory)rec;
if (ah != null)
{
string[] fields = ah.ModifiedFields.Split('\0');
for (int i = 0; i < fields.GetUpperBound(0); i+=2)
{
osd = new xTACOpenSourceDetail();
osd.OpenSourceName = "AuditHistoryTable";
osd.DataID = "1";
osd.String01 = Convert.ToString(PX.Common.PXContext.GetSlot<int?>("singleCompanyID"));
osd.Number01 = ah.BatchID;
osd.Number02 = ah.ChangeID;
osd.Number03 = recordID;
osd.String02 = ah.ScreenID;
osd.String03 = Convert.ToString(ah.UserID);
osd.Date01 = ah.ChangeDate;
osd.String04 = ah.Operation;
osd.String05 = ah.TableName;
osd.String06 = ah.CombinedKey;
osd.String07 = fields[i];
osd.String08 = fields[i + 1];
osd.String09 = Convert.ToString(ah.ChangeDate);
osdm.OpenSourceDataDetail.Insert(osd);
//if (osd != null)
//osdm.Actions.PressSave();
recordID++;
}
recordID = 1;
//osdm.Actions.PressSave();
}
//osdm.Actions.PressSave();
}
osdm.Actions.PressSave();
});
return adapter.Get();
}
Any ideas?
A couple of things you could try:
Instead of instantiating osd directly, replace with:
osd = osdm.OpenSourceDataDetail.Insert();
and then after assigning values and prior to PressSave(), use .Update on the cache:
osdm.OpenSourceDataDetail.Update(osd);
and then after PressSave(), clear the graph:
osdm.Clear();
Importantly, does your DAC have an IsKey = true on the DataID field and marked PXDBIdentity, hence auto-assigned and unique?
Example:
public abstract class dataID : PX.Data.IBqlField {}
[PXDBIdentity(IsKey = true)]
[PXUIField(Visible = false, Enabled = false)]
public virtual int? DataID {get; set;}
If the changes are successful, you could then try moving the .PressSave() and .Clear() after the For loop.

The bot doesn't understand the value when I use the property Describe

I am using the sample "SandwichOrder" code. When I use the property "Describe" to change the item value the bot doesn't understand the setted value.
public enum LengthOptions
{
[Describe("Test 1")]
SixInch = 1,
[Describe("Test 2")]
FootLong = 2
};
This is the output:
It's the problem how FormFlow handles the feedback after user's selection, the result is actually right the type of LengthOptions. Since we're not able to modify the source code of BotBuilder SDK, here is a workaround to solve this problem: we try to override the feedback of this item in FormFlow, and here is the code when building the FormDialog:
...
.Field(nameof(Length),
validate: async (state, response) =>
{
var result = new ValidateResult { IsValid = true, Value = response };
var value = (LengthOptions)response;
result.Feedback = "Your selection means " + value;
return result;
})
...
The Length property in above code can be defined like this:
public enum LengthOptions
{
[Describe("Test 1")]
SixInch = 1,
[Describe("Test 2")]
FootLong = 2
};
public LengthOptions? Length { get; set; }
Here is the test result:
What #Grace Feng mentioned is one way to do that. Another simpler way would be to add the Terms decoration to LengthOptions each item.
So the code would be :
public enum LengthOptions
{
[Terms(new string[] { "Test 1" })]
[Describe("Test 1")]
SixInch = 1,
[Terms(new string[] { "Test 2" })]
[Describe("Test 2")]
FootLong = 2
};
Now your bot will automatically understand the value of "Test 1" as SixInch and "Test 2" as FootLong

How to batch get items using servicestack.aws PocoDynamo?

With Amazon native .net lib, batchget is like this
var batch = context.CreateBatch<MyClass>();
batch.AddKey("hashkey1");
batch.AddKey("hashkey2");
batch.AddKey("hashkey3");
batch.Execute();
var result = batch.results;
Now I'm testing to use servicestack.aws, however I couldn't find how to do it. I've tried the following, both failed.
//1st try
var q1 = db.FromQueryIndex<MyClass>(x => x.room_id == "hashkey1" || x.room_id == "hashkey2"||x.room_id == "hashkey3");
var result = db.Query(q1);
//2nd try
var result = db.GetItems<MyClass>(new string[]{"hashkey1","hashkey2","hashkey3"});
In both cases, it threw an exception that says
Additional information: Invalid operator used in KeyConditionExpression: OR
Please help me. Thanks!
Using GetItems should work as seen with this Live Example on Gistlyn:
public class MyClass
{
public string Id { get; set; }
public string Content { get; set; }
}
db.RegisterTable<MyClass>();
db.DeleteTable<MyClass>(); // Delete existing MyClass Table (if any)
db.InitSchema(); // Creates MyClass DynamoDB Table
var items = 5.Times(i => new MyClass { Id = $"hashkey{i}", Content = $"Content {i}" });
db.PutItems(items);
var dbItems = db.GetItems<MyClass>(new[]{ "hashkey1","hashkey2","hashkey3" });
"Saved Items: {0}".Print(dbItems.Dump());
If your Item has both a Hash and a Range Key you'll need to use the GetItems<T>(IEnumerable<DynamoId> ids) API, e.g:
var dbItems = db.GetItems<MyClass>(new[]{
new DynamoId("hashkey1","rangekey1"),
new DynamoId("hashkey2","rangekey3"),
new DynamoId("hashkey3","rangekey4"),
});
Query all Items with same HashKey
If you want to fetch all items with the same HashKey you need to create a DynamoDB Query as seen with this Live Gistlyn Example:
var items = 5.Times(i => new MyClass {
Id = $"hashkey{i%2}", RangeKey = $"rangekey{i}", Content = $"Content {i}" });
db.PutItems(items);
var rows = db.FromQuery<MyClass>(x => x.Id == "hashkey1").Exec().ToArray();
rows.PrintDump();

converting strings within Entity Framework query

The following query works perfectly fine and populates its dropdown list. The data in the data base is stored in all uppercase, ie PALM BEACH. I want to convert it to Proper case, which obviously i can do after the fact by iterating through the returned list and reformatting BUT I should be able to do it with in the query itself. The following query works fine.
Dim citylist As List(Of String) = (From c In ctx.ziptaxes
Where c.StateID = ddlStates.SelectedIndex
Order By c.City Ascending
Select c.City).ToList()
But if i try to convert it to some thing like this, it fails
Dim citylist As List(Of String) = (From c In ctx.ziptaxes
Where c.StateID = ddlStates.SelectedIndex
Let cityname = StrConv(c.City, VbStrConv.ProperCase)
Order By cityname Ascending
Select cityname).ToList()
I've tried using culture info and String.Format(c.City, vbProperCase) too and nothing other than the original query works. Any help appreciated.
ADDENDUM:
Well some further research is telling me that .Net objects like string conversion and cultureinfo cannot be used prior to the query being run. If that's the case it explains why it isn't working. The following solves my problem BUT I would still like to know if there is way to do it within the LINQ to EF.
Dim citylist As List(Of String) = (From c In ctx.ziptaxes
Where c.StateID = ddlStates.SelectedIndex
Order By c.City Ascending
Select c.City).ToList()
If citylist.Count > 0 Then
For i As Integer = 0 To citylist.Count - 1
citylist(i) = StrConv(citylist(i).ToLower(), vbProperCase)
Next
With ddlCity
.Items.Clear()
.DataSource = citylist.Distinct()
.DataBind()
.Items.Insert(0, "Select a city")
.SelectedIndex = 0
End With
End If
You can do the conversion in your SELECT. Here's an example (with an over-simplified City name converter):
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace LinqQuestion
{
[TestFixture]
public class StackOverflowTests
{
private IEnumerable<City> _cities;
[TestFixtureSetUp]
public void Arrange()
{
_cities = new List<City>
{
new City { Id = 1, Name = "FLINT", StateId = 1 },
new City { Id = 2, Name = "SAGINAW", StateId = 1 },
new City { Id = 3, Name = "DETROIT", StateId = 1 },
new City { Id = 4, Name = "FLint", StateId = 1 }
};
}
[Test]
public void TestCountryQuery()
{
var data = _cities
.Where(c => c.StateId == 1)
.OrderBy(c => c.Name)
.Select(c => StrConv(c.Name))
.Distinct().ToList();
Assert.That(data.Count == 3);
}
private static string StrConv(string original)
{
var firstLetter = original.Substring(0, 1).ToUpper();
var theRest = original.Substring(1, original.Length - 1).ToLower();
return firstLetter + theRest;
}
}
public class City
{
public int Id { get; set; }
public int StateId { get; set; }
public string Name { get; set; }
}
}

Sequence Number for Id

I want to configure my Mongo DB to create sequence number for an Id column. Ex. It has to start from 1001 and increase by 1 automatically when I insert next row. I have my schema definitions as part of Node.JS how to add this configuration in Node schema?
MongoDB doesn't support this out of the box. The way I've implemented this (albeit in C#) is to create a "Sequence" collection with a key and a next number. You can atomically increment and return the next number then use this as the id in your collection.
This is a C# function, using the findandmodify mongodb command to fetch and update a sequence number for a given "key".
public long GetNextSequenceNumber(string name, string key)
{
var update = new BsonDocument(new BsonElement("$inc", new BsonDocument(new BsonElement("SequenceNumber", 1))));
var query = new BsonDocument("_id", key);
var command = new CommandDocument {
{ "findandmodify" , name },
{ "query", query},
{ "update" , update},
{ "new" , true},
};
var res = Db.RunCommand(command);
if (res.Response["value"] != BsonNull.Value)
{
var o = BsonSerializer.Deserialize<Sequence>(res.Response["value"].ToBsonDocument());
return o.SequenceNumber;
}
else
{
var o = new Sequence() { Id = key, SequenceNumber = 0 };
Db.GetCollection(name).Insert<Sequence>(o);
return o.SequenceNumber;
}
}
and the Sequence model:
public class Sequence
{
public string Id { get; set; }
public long SequenceNumber { get; set; }
}
The sequence documents look like:
{
_id : 'mykey',
SequenceNumber : NumberLong(1234)
}
If you need converting it to javascript please ask.
Hope that helps.

Resources