Saving changes if adding to context - entity-framework-core

EF Core 2.2.x
The question Im trying to answer is if I can limit my call of SaveChanges to only once or do I need it each time I add an entity, since in the next lower level I need to query the DbContext to see if the item exists.
Am trying to "merge" some data between two sets of similar tables. So in the process of adding records and drilling down the hierarchy, I have to first see if a record exists and if not, then add it.
/*
List<StagedOrder> sorders;
StagedOrder
Id
Number
InsertDate
Notes
StagedOrderLine
Id
ItemNo
Qty
StagedOrderKey
*/
foreach( StagedOrder s in sorders) {
// get order
Order o = (from a in ctx.Orders where a.number = s.Number select a).FirstOrDefault();
if(o == null) {
o = new Order() {
Number = s.Number,
InsertDate = DateTime.Now,
Notes = "imported order"
};
ctx.Orders.Add(o);
// ctx.SaveChanges() ???
}
// get lines for orders
OrderLines ol = (from a in ctx.DetailLines where a.OrderKey = o.Id select a).ToList();
if(ol == null) {
foreach(StagedOrderLine l in s.StagedOrderLines) {
ol = new DetailLine() {ItemNo = l.ItemNo, Qty = l.Qty, OrderKey = l.Id}
ctx.DetailLines.Add(ol);
ctx.SaveChanges();
}
}
}
// can I limit my call to savechanges only once here?
// ctx.SaveChanges();
Also, should I create a new DetailLine using an Order.Id or an entity reference, eg.
ol = new DetailLine() {ItemNo = l.ItemNo, Qty = l.Qty, Order = o}

Related

Test Class in apex

So i am new to salesforce and i finished my training and now im working on a project. But on my project i have stumbled on a test class that i am not finding a way to write it, so i would appreciate if anyone can help me figure out a way to write it. Here is the code:
public class AP01_Opportunity
{
//Method to create a new service contract when opportunity = Gagné
public static void CreateContract(List<Opportunity> listOpp, Map<Id, Opportunity> oldMap)
{
//Variable Declaration
ServiceContract sc;
List<ServiceContract> listSCToAdd = new List<ServiceContract>();
List<ContractLineItem> listContractItems = new List<ContractLineItem>();
List<Opportunity> listOppGagne = new list<Opportunity>();
//Loop in list of opportunities
for(Opportunity opp : listOpp)
{
if(opp.StageName == Label.ClotureGagne && !oldMap.get(opp.Id).isWon)
{
listOppGagne.add(opp);
}
}
//check if list has opportunity becoming won
if(listOppGagne.size() > 0){
Map<Id, Opportunity> mapOppGagne = new Map<Id, Opportunity> ([SELECT Id,
Name,
StageName,
Pricebook2Id,
Account.Name,
(SELECT Id,
PricebookEntryId,
PricebookEntry.Name,
Quantity,
UnitPrice
FROM OpportunityLineItems)
FROM Opportunity
WHERE Id in :listOppGagne]);
for( Opportunity opp : listOppGagne )
{
//Create new service contract
sc = new ServiceContract();
sc.Name = opp.Name;
sc.ApprovalStatus = Label.Activated;
sc.OpportunityId__c = Id.valueOf(opp.Id);
sc.Pricebook2Id = opp.Pricebook2Id;
sc.StartDate = Date.today();
listSCToAdd.add(sc);
}
if(listSCToAdd.size() > 0){
insert listSCToAdd;
Opportunity currentOpp;
ContractLineItem cli;
Id oppId;
for(ServiceContract servcont : listSCToAdd)
{
oppId = servcont.OpportunityId__c;
if(mapOppGagne.containsKey(oppId))
{
currentOpp = mapOppGagne.get(oppId);
//copy the oppLineItems per opportunity to the respective Service Contract
for(OpportunityLineItem items : currentOpp.OpportunityLineItems)
{
cli = new ContractLineItem();
cli.PricebookEntryId = items.PricebookEntryId;
cli.Quantity = items.Quantity;
cli.UnitPrice = items.UnitPrice;
cli.ServiceContractId = servcont.Id;
listContractItems.add(cli);
}
}
}
if(listContractItems.size() > 0)
{
insert listContractItems;
}
}
}
}
}
this code is a trigger that creates a new service contract record with contract line items copied from the opportunity line items, when the opportunity stage changes to "Cloturé Gagné" which means closed won in french.
Thank you in advance.
In order to write a simple test class I would recommend you to use the following guide: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_qs_test.htm
The idea is simple: Lets say you create an Opportunity in your Test class and make an insert or update in your case - your trigger class will automatically fire and run the code from your AP01_Opportunity class. You can put some
System.debug('some message');
to check if your logic works as expected and also which code blocks are executed

How to avoid writing duplicate queries when needing counts and then items

I have constructed a LINQ query that joins about a half dozen tables. The problem is, for paging purposes, I want to get a count first of how many items will be returned. So the issue I'm running into is having to write the exact same query twice: one to get the item count then another to build my collection of items.
Example:
using (var context = new DbContext())
{
var items = from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
etc.
etc.
select i;
count = items.Count();
}
return count;
.
.
.
using (var context = new DbContext())
{
var items = from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
etc.
etc.
select new
{
DynamicProp1 = i.SomeProperty,
DyanmicProp2 = a.SomeProperty,
DyanmicProp3 = b.SomePropery,
etc.
etc.
}
... do some stuff with 'items'...
}
I cannot think of any way to avoid this duplicate query. I need access to all the joined tables in order to build my collection. I would appreciate any tips or suggestions.
You can create method which get context and return IQueryable of items with all needed entities:
class Holder
{
TableAItem A{get;set;}
TableBItem B{get;set;}
...
}
IQueryable<Holder> GetQuery(DbContext context)
{
return from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
...
select new Holder
{
A = i,
B = b
....
};
}
using (var context = new DbContext())
{
var items = GetQuery(context);
count = items.Count();
}
return count;
using (var context = new DbContext())
{
var items = from r in GetQuery(context)
select new
{
DynamicProp1 = r.a.SomeProperty,
DyanmicProp2 = r.a.SomeProperty,
DyanmicProp3 = r.b.SomePropery,
etc.
etc.
}
... do some stuff with 'items'...
}
Remember that making a query doesn't execute it, this is called deferred execution. So why not make the query and then pass it around as an IQueryable<> object. For example, consider this code:
Just a simple method to return the last char from a string, but it also writes out what it's doing:
public char GetLastChar(string input)
{
Console.WriteLine("GetLastChar from {0}", input);
return input.Last();
}
Now this code using the method:
var listOfStuff = new List<string> { "string1", "string2", "string3" };
Console.WriteLine("Making the query");
var results = from s in listOfStuff
select GetLastChar(s);
Console.WriteLine("Before getting count");
var count = results.Count();
Console.WriteLine("Now enumerating the query");
foreach(var s in results)
{
Console.WriteLine(s);
}
You will see the output as follows:
Making the query
Before getting count
GetLastChar from string1
GetLastChar from string2
GetLastChar from string3
3
Now enumerating the query
GetLastChar from string1
1
GetLastChar from string2
2
GetLastChar from string3
3

get primary key of last inserted record with JPA

I've been using JPA to insert entities into a database but I've run up against a problem where I need to do an insert and get the primary key of the record last inserted.
Using PostgreSQL I would use an INSERT RETURNING statement which would return the record id, but with an entity manager doing all this, the only way I know is to use SELECT CURRVAL.
So the problem becomes, I have several data sources sending data into a message driven bean (usually 10-100 messages at once from each source) via OpenMQ and inside this MDB I persists this to PostgreSQL via the entity manager. It's at this point I think there will be a "race condition like" effect of having so many inserts that I won't necessarily get the last record id using SELECT CURRVAL.
My MDB persists 3 entity beans via an entity manager like below.
Any help on how to better do this much appreciated.
public void onMessage(Message msg) {
Integer agPK = 0;
Integer scanPK = 0;
Integer lookPK = 0;
Iterator iter = null;
List<Ag> agKeys = null;
List<Scan> scanKeys = null;
try {
iag = (IAgBean) (new InitialContext()).lookup(
"java:comp/env/ejb/AgBean");
TextMessage tmsg = (TextMessage) msg;
// insert this into table only if doesn't exists
Ag ag = new Ag(msg.getStringProperty("name"));
agKeys = (List) (iag.getPKs(ag));
iter = agKeys.iterator();
if (iter.hasNext()) {
agPK = ((Ag) iter.next()).getId();
}
else {
// no PK found so not in dbase, insert new
iag.addAg(ag);
agKeys = (List) (iag.getPKs(ag));
iter = agKeys.iterator();
if (iter.hasNext()) {
agPK = ((Ag) iter.next()).getId();
}
}
// insert this into table always
iscan = (IScanBean) (new InitialContext()).lookup(
"java:comp/env/ejb/ScanBean");
Scan scan = new Scan();
scan.setName(msg.getStringProperty("name"));
scan.setCode(msg.getIntProperty("code"));
iscan.addScan(scan);
scanKeys = (List) iscan.getPKs(scan);
iter = scanKeys.iterator();
if (iter.hasNext()) {
scanPK = ((Scan) iter.next()).getId();
}
// insert into this table the two primary keys above
ilook = (ILookBean) (new InitialContext()).lookup(
"java:comp/env/ejb/LookBean");
Look look = new Look();
if (agPK.intValue() != 0 && scanPK.intValue() != 0) {
look.setAgId(agPK);
look.setScanId(scanPK);
ilook.addLook(look);
}
// ...
The JPA spec requires that after persist, the entity be populated with a valid ID if an ID generation strategy is being used. You don't have to do anything.

Entity Framework, one-to-many, several columns

If I have a main table, lets say orders, and a sub table of items and the items table has a fields for item number BUT it also has a nullable (optional) field for color that applied only to certain items. How would I update the items table, at the same time as the orders table, using Entity Framework?
Here is a code example of what I have so far. Two problems, I'm only entering one of my items and, from what my research indicates, I can't add another field to the items table?
foreach (Guid c in AllItems)
{ Items.OrderItemID = Guid.NewGuid();
ITemsOrderID = order.OrderID;
ITems.ItemID = c;
If (ItemID = ItemThatLetsYouChoseAColorID)
{
Items.ItemColorID = ColorID;
} else {
Items.ItemColorID = null;
}
}
context.Orders.AddObject(Orders);
context.Items.AddObject(Items);
context.SaveChanges();
My Orders table gets a record inserted, and the Items gets ONE record inserted. I'm missing something basic here, I'm afraid. BTW, this is Entity Framework 4.0, which. I believe, does not require the use of EntityKey.
You're adding an object to the Items collection only one time after the scope of your foreach.
Have you tested something like:
foreach (Guid c in AllItems)
{
var Item = new Item();
Item.OrderItemID = Guid.NewGuid();
Item.OrderID = order.OrderID;
Item.ItemID = c;
If (ItemID = ItemThatLetsYouChoseAColorID)
{
Item.ItemColorID = ColorID;
}
else
{
Item.ItemColorID = null;
}
context.Items.AddObject(Items);
}
context.Orders.AddObject(order);
context.SaveChanges();
And I'm not sure to understand what you mean by
I can't add another field to the items table
You should be more precise about what you actually expect. Insert a row, add a column in the table...? What is a "field"?
Here is the working code. I had the new Item outside the foreach item loop, so was overwriting the value. Also, I need to add each one to the context. I had a hard time with this, hope it helps someone else:
<-fill the order object->
foreach (Guid i in Items)
{
**Items item = new Items();**
item.ItemID = Guid.NewGuid();
item.OrderID = order.OrderID;
if (i == ItemWithColorGuid)
{
foreach (Guid c in Colors)
{
**Items color = new Items();**
color.ItemsID = Guid.NewGuid();
color.OrderID = order.orderID;
color.itemID = g;
color.colorID = c;
context.item.AddObject(color);
}
}
else
{
item.ItemID = i;
item.ColorID = null;
context.item.AddObject(item);
}
}
context.orders.AddObject(order);
context.SaveChanges();

.Net Entity Framework SaveChanges is adding without add method

I'm new to the entity framework and I'm really confused about how savechanges works. There's probably a lot of code in my example which could be improved, but here's the problem I'm having.
The user enters a bunch of picks. I make sure the user hasn't already entered those picks.
Then I add the picks to the database.
var db = new myModel()
var predictionArray = ticker.Substring(1).Split(','); // Get rid of the initial comma.
var user = Membership.GetUser();
var userId = Convert.ToInt32(user.ProviderUserKey);
// Get the member with all his predictions for today.
var memberQuery = (from member in db.Members
where member.user_id == userId
select new
{
member,
predictions = from p in member.Predictions
where p.start_date == null
select p
}).First();
// Load all the company ids.
foreach (var prediction in memberQuery.predictions)
{
prediction.CompanyReference.Load();
}
var picks = from prediction in predictionArray
let data = prediction.Split(':')
let companyTicker = data[0]
where !(from i in memberQuery.predictions
select i.Company.ticker).Contains(companyTicker)
select new Prediction
{
Member = memberQuery.member,
Company = db.Companies.Where(c => c.ticker == companyTicker).First(),
is_up = data[1] == "up", // This turns up and down into true and false.
};
// Save the records to the database.
// HERE'S THE PART I DON'T UNDERSTAND.
// This saves the records, even though I don't have db.AddToPredictions(pick)
foreach (var pick in picks)
{
db.SaveChanges();
}
// This does not save records when the db.SaveChanges outside of a loop of picks.
db.SaveChanges();
foreach (var pick in picks)
{
}
// This saves records, but it will insert all the picks exactly once no matter how many picks you have.
//The fact you're skipping a pick makes no difference in what gets inserted.
var counter = 1;
foreach (var pick in picks)
{
if (counter == 2)
{
db.SaveChanges();
}
counter++;
}
I've tested and the SaveChanges doesn't even have to be in the loop.
The below code works, too.
foreach (var pick in picks)
{
break;
}
db.SaveChanges()
There's obviously something going on with the context I don't understand. I'm guessing I've somehow loaded my new picks as pending changes, but even if that's true I don't understand I have to loop over them to save changes.
Can someone explain this to me?
Here's updated working code based on Craig's responses:
1) Remove the Type then loop over the results and populate new objects.
var picks = (from prediction in predictionArray
let data = prediction.Split(':')
let companyTicker = data[0]
where !(from i in memberQuery.predictions
select i.Company.ticker).Contains(companyTicker)
select new //NO TYPE HERE
{
Member = memberQuery.member,
Company = db.Companies.Where(c => c.ticker == companyTicker).First(),
is_up = data[1] == "up", // This turns up and down into true and false.
}).ToList();
foreach (var prediction in picks)
{
if (includePrediction)
{
var p = new Prediction{
Member = prediction.Member,
Company = prediction.Company,
is_up = prediction.is_up
};
db.AddToPredictions(p);
}
}
2) Or if I don't want the predictions to be saved, I can detach the predictions.
foreach (var prediction in picks) {
if (excludePrediction)
{
db.Detach(prediction)
}
}
The reason is here:
select new Prediction
{
Member = memberQuery.member,
These lines will (once the IEnumerable is iterated; LINQ is lazy) :
Instantiate a new Prediction
Associate that Prediction with an existing Member, *which is attached to db.
Associating an instance of an entity with an attached entity automatically adds that entity to the context of the associated, attached entity.
So as soon as you start iterating over predictionArray, the code above executes and you have a new entity in your context.