What is the best way of handling trying to get data from a DataReader that has more than one column with the same name?
Because of the amount of work involved and because we don't want to lose support from a vendor by changing the stored procedures we are using to retrieve the data, I am trying to find another way to get access to a column that shows up more than once in a datareader without having to rewrite the stored procedures.
Any Ideas?
EDIT:
Ok, the function that actually populates from a datareader is used in multiple places so there is a possibility that the function can be called by different stored procedures. What I did was to do a GetName using the index to check if it is the correct column, and if it is, then pull its value.
If you know the index of the column, then access it by the index.
Can't you use column ordinals? 0 for the 1st, 1 for the 2nd, and so on?
You will have to reference the column by index no; i.e. reader[5].ToString(); to read the data in column 5.
Based on original poster's approach described in the "Edit" paragraph, here's an extension method that will give the value based on the column name and the index of that name, e.g., 0 for the first instance of name, 1 for the second, etc:
using System;
namespace WhateverProject {
internal static class Extentions {
// If a query returns MULTIPLE columns with the SAME name, this allows us to get the Nth value of a given name.
public static object NamedValue(this System.Data.IDataRecord reader, string name, int index) {
if (string.IsNullOrWhiteSpace(name)) return null;
if (reader == null) return null;
var foundIndex = 0;
for (var i = 0; i < reader.FieldCount; i++) {
if (!reader.GetName(i).Equals(name, StringComparison.CurrentCultureIgnoreCase)) continue;
if (index == foundIndex) return reader[i];
foundIndex++;
}
return false;
}
}
}
Use it thus:
var value1 = reader.NamedValue("duplicatedColumnName", 0);
var value2 = reader.NamedValue("duplicatedColumnName", 1);
Related
The Flutter Hashmap<String, List > is overwritting the List type in the Hashmap on every single unique Key. So basically the code looks like:
HashMap<String, List<Jobs> > ElementJobMap = new HashMap<String, List<Jobs> >();
for (int i = 0; i < _JobsList.length; i++) {
String Key = _JobsList[i].elementID.toString();
if (ElementJobMap.containsKey(Key)) {
if (Key == _JobsList[i].elementID.toString()) {
ElementJobMap.update(Key, (value) {
value.add(_JobsList[i]);
return value;
});
}
} else {
ElementJobMap[Key] = tmpList;
}
}
So if there are two different String keys and each key has a unique list filled with several values in each. If I write to the List every instance is updated and so it is all the same data. Should be noted this code might have pointless stuff in it, but I've just been spitballing for awhile now.
ElementJobMap[Key] = tmpList;
You seem to be adding the exact same list instance as a base of every entry. Then you add to this single instance. If you want your hashmap to hold a different list instance for every key, you need to actually create one.
That said, you should probably look into grouping methods provided through packages, there is no point in trying to do it manually.
I have an order table and an orderStatus table, their relationship is as seen below:
I want to be able to change 'StatusID' to the value 2, of a specific order (i am able to get the specific order ID, and have loaded it into an integer variable) using a lambda expression within an action result - would there be any easy way of doing this?
So far i have tried:
//get specific order ID
int currentOrderId = newConfirmedOrderLine.OrderID;
//-----
Order statusChange = new Order();
statusChange.OrderStatus.StatusID = 2;
DBAccessor.SaveChanges();
I am new to linq and lambda, so any explanation with an answer would be greatly appreciated!
If DBAccessor is a DbContext then this could/should work. You need to load the Order entity that you want to change from the DBAccessor.Order DbSet, change it by setting a property, and then call SaveChanges.
var orderStatus = DBAccessor.OrderStatus.First(x => x.StatusID == 2);
var order = DBAccessor.Order.Find(currentOrderId);
order.OrderStatus = orderStatus;
DBAccessor.SaveChanges();
Given a simple table
create table car (
make varchar
model varchar
)
And the following DAO code
NamedParameterJdbcTemplate template;
String SQL = "delete from car where make = :make and model in (:model)";
void batchDelete(final Map<String, Collection<String>> map) {
SqlParameterSource[] params = map.entrySet().stream()
.map(entry -> toParams(entry.getKey(), entry.getValue()))
.toArray(SqlParameterSource[]::new);
template.batchUpdate(SQL, params);
}
void delete(final Map<String, Collection<String>> map) {
map.forEach((make, models) -> {
SqlParameterSource params = toParams(make, models);
template.update(SQL, params);
});
}
SqlParameterSource toParams(final String make, final Collection<String> models) {
return new MapSqlParameterSource("make", make)
.addValue("model", new ArrayList<>(models));
}
The batch delete function fails when the maps has 2 keys with different number of values for the IN clause in a batch. Assume Map.of creates and ordered Map.
// runs fine - 2 values for each key
batchDelete(Map.of("VW", Arrays.asList("Polo", "Golf"), "Toyota", Arrays.asList("Yaris", "Camry")));
// fails - first key has 1 value, second key has 2 values
batchDelete(Map.of("Toyota", Arrays.asList("Yaris"), "VW", Arrays.asList("Polo", "Golf")));
// runs fine - key with bigger list comes first
batchDelete(Map.of("VW", Arrays.asList("Polo", "Golf"), "Toyota", Arrays.asList("Yaris")));
// non batch delete runs fine either way
delete(Map.of("Toyota", Arrays.asList("Yaris"), "VW", Arrays.asList("Polo", "Golf")));
Spring documentation sort of alludes to that
https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#jdbc-in-clause
The SQL standard allows for selecting rows based on an expression that includes a variable list of values. A typical example would be select * from T_ACTOR where id in (1, 2, 3). This variable list is not directly supported for prepared statements by the JDBC standard; you cannot declare a variable number of placeholders. You need a number of variations with the desired number of placeholders prepared, or you need to generate the SQL string dynamically once you know how many placeholders are required. The named parameter support provided in the NamedParameterJdbcTemplate and JdbcTemplate takes the latter approach.
The error message is
The column index is out of range: 3, number of columns: 2.; nested exception is org.postgresql.util.PSQLException: The column index is out of range: 3, number of columns: 2.
What happens is the following line in NamedParameterJdbcTemplate # batchUpdate:
PreparedStatementCreatorFactory pscf = getPreparedStatementCreatorFactory(parsedSql, batchArgs[0]);
will create a dynamic sql out of the first batch arg length:
delete from car where make = ? and model in (?)
So the 2nd batch item which has 2 models will fail as there is only 1 placeholder.
What would be a workaround ? (other than grouping map entries by number of values)
Solution
Went back to plain old PreparedStatement
SQL - use ANY instead of IN
delete from car where make = ? and model = any (?)
DAO
Connection con;
PreparedStatement ps = con.prepareStatement("SQL");
map.forEach((make, models) -> {
int col = 0;
ps.setString(++col, make);
ps.setArray(++col, con.createArrayOf("text", models));
ps.addBatch();
});
ps.executeBatch();
I would recommend changing the SQL to look something more like this:
String SQL = "DELETE FROM car WHERE (make, model) IN (:ids)";
If you do it this way then you can use something similar to the answer I gave on this question: NamedJDBCTemplate Parameters is list of lists. Doing it this way means you can use NamedParameterJdbcTemplate.update(String sql, Map<String, ?> paramMap). Where in your paramMap the key would be "ids" and the value would be an instance of Collection<Object[]> where each entry in the collection is an array containing the value pairs you want to delete:
List<Object[]> params = new ArrayList<>();//you can make this any instance of collection you want
for (Car car : cars) {
params.add(new Object[] { car.getMake(), car.getModel() });
//this is just to provide an example of what I mean, obviously this will probably be different in your app.
}
Following is the code, I am trying:
public List<Movie> GetMovies()
{
Func<Movie, Movie> prepareMovieOutput =
(input) =>
{
input.DisplayHtmlContent = String.Empty;
return input;
};
var moviesOutput = from m in db.Movies.ToList()
select prepareMovieOutput(m);
return moviesOutput.ToList();
}
public List<Movie> SearchMovies(string searchTerm)
{
var moviesOutput = db.Movies.Where(m => m.Name.Contains(searchTerm)).ToList();
return moviesOutput.ToList();
}
The GetMovies function is working properly, as it returns List collection after clearing DisplayHtmlContent field, whereas, SearchMovies function is supposed to return Movie collection with DisplayHtmlContent field, but inspite of that it returns that field empty.
If I set DisplayHtmlContent to some fixed value (like, "ABC"),both GetMovies and SearchMovies return the list with all Movie having DisplayHtmlContent field as "ABC" value. I don't understand why the function defined in one method should affect the other one. and also how to fix this issue?
Ideally, I want GetMovies to hold all Movie with that particular field as empty string, and SearchMovies to hold all Movie with that field containing value.
Any help on this much appreciated.
this was due to the use of repository. I have removed it and it started working fine. with having EF 5, I didn't need to use repository
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();