I need to have a Map within a Map in one of my Triggers. I know how to build the map initially as that is documented:
Map<Id, Map<Id, Addendum__c>> addendums = new Map<Id, Map<Id, Addendum__c>>{};
However, I'm having trouble actually assigning values to the multi-dimensional map. Normally I would use .put() to place values into a single-dimension map. Maybe I'm still supposed to use that function, but I can't for the life of me figure out the correct syntax.
I have tried the following which do not work:
addendums.put(addendum.Opportunity__c, addendum.Id, addendum);
addendums.put(addendum.Opportunity__c, (addendum.Id, addendum));
Does anyone know how to do this?
Thanks!
Using your sample code will work, though of course the second level map can only ever have one entry, if you're in a situation where you may need more then the code below will do the trick for you:
// This line creates the map in the proper format
Map<Id,Map<Id,Addendum__c>> addendums = new Map<Id,Map<Id,Addendum__c>>{};
// This for loop goes through each addendum and first places it into a single dimension map.
// Once that map is created, it is placed into the final multi-dimensional
for(Addendum__c addendum : [SELECT Id,Opportunity__c FROM Addendum__c WHERE Opportunity__c IN :oppBatch])
{
if(addendums.get(addendum.Opportunity__c) == null)
{
addendums.put(addendum.Opportunity__c, new Map<Id, Addendum__c>{addendum.Id => addendum);
}
else
{
addendums.get(addendum.Opportunity__c).put(addendum.Id, addendum);
}
}
As you can see this uses get() on the first map to get the correct second-level map in which we want to add an addendum.
The method that Josh provided works fine. If you'd like to remove one line of code, you can using the following approach:
Map<Id, Map<Id,Addendum__c>> addendums = new Map<Id,Map<Id,Addendum__c>>();
for(Addendum__c addendum : [SELECT Id,Opportunity__c FROM Addendum__c WHERE Opportunity__c IN :oppBatch]){
addendums.put(addendum.opportunity__c, new Map<Id, Addendum__c>{addendum.id => addendum});
}
I'm not aware of a more efficient approach than this one.
I found a way to do this, although I'm not sure whether it's the most efficient method or not. So if you have a better solution, please let me know, I'll transfer the "solution" credit to you.
What I did was assign the first map, and then place that map within the final map.
// This line creates the map in the proper format
Map<Id,Map<Id,Addendum__c>> addendums = new Map<Id,Map<Id,Addendum__c>>{};
// This for loop goes through each addendum and first places it into a single dimension map.
// Once that map is created, it is placed into the final multi-dimensional
for(Addendum__c addendum : [SELECT Id,Opportunity__c FROM Addendum__c WHERE Opportunity__c IN :oppBatch]){
Map<Id,Addendum__c> thisAddendum = new Map<Id,Addendum__c>{ addendum.Id => addendum };
addendums.put(addendum.Opportunity__c,thisAddendum);
}
Related
I have a simple map:
Map<String, bool> selection = {};
Then the entries are added. At some point, I want to reset the selection, i.e.:
for (var name in selection.keys) {
selection[name] = false;
}
Is there a way to reset all values to false, using only one statement in Dart?
For example, the following statement is very convenient and concise to check if the selection is not empty:
if( selection.values.contains(true) )
So, I'm looking for something similar to reset all the values to false.
Rk: Out of scope answers ;-) :
forEach
writing a method/function to do the stuff
Addendum
Just found and tested this one:
selection.updateAll((name, value) => value = false);
But there could be something even more simple, i.e. without a lambda function.
selection.forEach((name, value) => selection[name] = false);
I think this is what you are looking for. A simple one-liner solution.
Edit
selection.updateAll((name, value) => value = false);
is also an option, as you have added
Apart from those, there is no predefined method that allows you to achieve what you have described.
I don't understand why it is recommended everywhere to use AddOrUpdate in the Seed method?
We develop application for half a year already and the AddOrUpdates overwrites user changes every time we update the server. E.g. if we call in the Seed:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
And user changes the Style to "green" then on next server update we overwrite it to "red" again and we get very annoyed user.
It looks that if we change AddOrUpdate to Add we will be guaranteed from overwriting user data. If we still need some special case we can put it to separate migration. Unlike the general Configuration.Seed method particular migrations don't run twice over the same database version.
I assume that Style's primary key is Id. The overload of AddOrUpdate that you use only checks if there is a record having Id == 1. If so, it updates it. That's all.
What's going wrong here is that the primary key is a surrogate key, i.e. it's there for querying convenience, but it's got no business meaning. Usually, with migrations you want to look for the natural keys of entities though. That's how the user identifies data. S/he wants a green style, not a style identified by 1.
So I think you should use this overload of AddOrUpdate:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
Now when there is no red style anymore, a new one is inserted, overriding the Id value (assuming that it's generated by the database).
From your later comments I understand that you want to Add data when they're new, but not update them when they exist (compared by primary key). For this you could use a slightly adapted version of an AddWhenNew method I described here. For your case I would do it like so:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
Re-fetching the item from the local collection is a nuisance, but necessary because of a bug in AddOrUpdate(). This bug also caused the error you got when setting the state of the original entry to Unchanged: it was a different instance than the attached one.
The way Add method acts is misleading. It Inserts data into database even if there is already a row with the same PrimaryKey as we do Add. It just creates new PrimaryKey ignoring our value silently. I should have tried it before asking the question, but anyway, I think I'm not the only one who confused by this. So, in my situation Add is even worse than AddOrUpdate.
The only solution I've come to is following:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}
I'm using the ListDataProvider example here as a guide. The columns are sorting fine as expectd based on the provided comparators. I'm trying to programatically apply a sort as alluded to on this line from the example:
// We know that the data is sorted alphabetically by default.
table.getColumnSortList().push(nameColumn);
What this does, is it makes the cell column appear to be sorted with the carrot sort indicator. However, the underlying data isn't sorted. Is there a way to get the table to actually apply the sort progarmatically. I suppose I could use this in conjunction with actually sorting the data via Collections.sort(), but I'd like to avoid that and do it in one place.
You can apply sorting on a column programatically with little exta code. The following code snippet does that -
When ever u set data to the cellTable you have to initialize the ListHandler as below code does -
cellTable.addColumnSortHandler( createColumnSortHandler() );
private ListHandler<T> createColumnSortHandler()
{
final ListHandler<T> listHandler = new ListHandler<T>(listDataProvider.getList());
listHandler.setComparator( sortColumn, comparator );
return listHandler;
}
And when u want to fire the SortEvent execute the following code snippet -
ColumnSortInfo columnSortInfo = new ColumnSortInfo( sortColumn, sortDirection );
cellTable.getColumnSortList().push( columnSortInfo );
ColumnSortEvent.fire( cellTable, cellTable.getColumnSortList());
you have to call setData on grid again.....
I'm trying to convert an entire column values into a arrayList using ormlite on android, is this possible, with direct api?
Using raw results i get close, but not quite:
GenericRawResults<String[]> rawResults =
getHelper().getMyProcessDao().queryRaw(
queryBuild.selectColumns("nid").prepareStatementString());
List<String[]> result = rawResults.getResults();
Hrm. I'm not sure this is what you want. However, one way to accomplish what you ask for specifically is through by using the RawRowMapper which can be passed to ORMLite's DAO method: dao.queryRaw(String, Rowmapper, String...).
Something like the following should work:
RawRowMapper<Integer> mapper = new RawRowMapper<Integer>() {
public Integer mapRow(String[] columnNames, String[] resultColumns) {
// maybe you should verify that there _is_ only 1 column here
// maybe you should handle the possibility of a bad number and throw
return Integer.parseInt(resultColumns[0]);
}
};
GenericRawResults<Integer> rawResults =
getHelper().getMyProcessDao().queryRaw(
queryBuild.selectColumns("nid").prepareStatementString(), mapper);
List<Integer> list = rawResults.getResults();
I have a very simple mapping function called "BuildEntity" that does the usual boring "left/right" coding required to dump my reader data into my domain object. (shown below) My question is this - If I don't bring back every column in this mapping as is, I get the "System.IndexOutOfRangeException" exception and wanted to know if ado.net had anything to correct this so I don't need to bring back every column with each call into SQL ...
What I'm really looking for is something like "IsValidColumn" so I can keep this 1 mapping function throughout my DataAccess class with all the left/right mappings defined - and have it work even when a sproc doesn't return every column listed ...
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim product As Product
While reader.Read()
product = New Product()
product.ID = Convert.ToInt32(reader("ProductID"))
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
Also check out this extension method I wrote for use on data commands:
public static void Fill<T>(this IDbCommand cmd,
IList<T> list, Func<IDataReader, T> rowConverter)
{
using (var rdr = cmd.ExecuteReader())
{
while (rdr.Read())
{
list.Add(rowConverter(rdr));
}
}
}
You can use it like this:
cmd.Fill(products, r => r.GetProduct());
Where "products" is the IList<Product> you want to populate, and "GetProduct" contains the logic to create a Product instance from a data reader. It won't help with this specific problem of not having all the fields present, but if you're doing a lot of old-fashioned ADO.NET like this it can be quite handy.
Although connection.GetSchema("Tables") does return meta data about the tables in your database, it won't return everything in your sproc if you define any custom columns.
For example, if you throw in some random ad-hoc column like *SELECT ProductName,'Testing' As ProductTestName FROM dbo.Products" you won't see 'ProductTestName' as a column because it's not in the Schema of the Products table. To solve this, and ask for every column available in the returned data, leverage a method on the SqlDataReader object "GetSchemaTable()"
If I add this to the existing code sample you listed in your original question, you will notice just after the reader is declared I add a data table to capture the meta data from the reader itself. Next I loop through this meta data and add each column to another table that I use in the left-right code to check if each column exists.
Updated Source Code
Using reader As SqlDataReader = cmd.ExecuteReader()
Dim table As DataTable = reader.GetSchemaTable()
Dim colNames As New DataTable()
For Each row As DataRow In table.Rows
colNames.Columns.Add(row.ItemArray(0))
Next
Dim product As Product While reader.Read()
product = New Product()
If Not colNames.Columns("ProductID") Is Nothing Then
product.ID = Convert.ToInt32(reader("ProductID"))
End If
product.SupplierID = Convert.ToInt32(reader("SupplierID"))
product.CategoryID = Convert.ToInt32(reader("CategoryID"))
product.ProductName = Convert.ToString(reader("ProductName"))
product.QuantityPerUnit = Convert.ToString(reader("QuantityPerUnit"))
product.UnitPrice = Convert.ToDouble(reader("UnitPrice"))
product.UnitsInStock = Convert.ToInt32(reader("UnitsInStock"))
product.UnitsOnOrder = Convert.ToInt32(reader("UnitsOnOrder"))
product.ReorderLevel = Convert.ToInt32(reader("ReorderLevel"))
productList.Add(product)
End While
This is a hack to be honest, as you should return every column to hydrate your object correctly. But I thought to include this reader method as it would actually grab all the columns, even if they are not defined in your table schema.
This approach to mapping your relational data into your domain model might cause some issues when you get into a lazy loading scenario.
Why not just have each sproc return complete column set, using null, -1, or acceptable values where you don't have the data. Avoids having to catch IndexOutOfRangeException or re-writing everything in LinqToSql.
Use the GetSchemaTable() method to retrieve the metadata of the DataReader. The DataTable that is returned can be used to check if a specific column is present or not.
Why don't you use LinqToSql - everything you need is done automatically. For the sake of being general you can use any other ORM tool for .NET
If you don't want to use an ORM you can also use reflection for things like this (though in this case because ProductID is not named the same on both sides, you couldn't do it in the simplistic fashion demonstrated here):
List Provider in C#
I would call reader.GetOrdinal for each field name before starting the while loop. Unfortunately GetOrdinal throws an IndexOutOfRangeException if the field doesn't exist, so it won't be very performant.
You could probably store the results in a Dictionary<string, int> and use its ContainsKey method to determine if the field was supplied.
I ended up writing my own, but this mapper is pretty good (and simple): https://code.google.com/p/dapper-dot-net/