LINQ deletion - Can delete one way, can't using Single enumerable - sql-server

This works:
var i = (from x in db.Test
where x.Id == 1
select x).First();
db.Test.DeleteOnSubmit(i);
db.SubmitChanges();
I get a cast error for this (int/string):
var i = db.Test.Single(x => x.Id == 1);
db.Test.DeleteOnSubmit(i);
db.SubmitChanges();
I was also able to make an update using Single sucesssfully on the same table. Any clues?
Update
Here's more of the error message:
Here's more:
[InvalidCastException: Unable to cast object of type 'System.Int32' to type 'System.String'.]
System.Data.Linq.SingleKeyManager2.TryCreateKeyFromValues(Object[] values, V& v) +134
System.Data.Linq.IdentityCache2.Find(Object[] keyValues) +57
System.Data.Linq.StandardIdentityManager.Find(MetaType type, Object[] keyValues) +51
System.Data.Linq.CommonDataServices.GetCachedObject(MetaType type, Object[] keyValues) +113
System.Data.Linq.ChangeProcessor.GetOtherItem(MetaAssociation assoc, Object instance) +235
System.Data.Linq.ChangeProcessor.BuildEdgeMaps() +510
System.Data.Linq.ChangeProcessor.SubmitChanges(ConflictMode failureMode) +137
System.Data.Linq.DataContext.SubmitChanges(ConflictMode failureMode) +453
System.Data.Linq.DataContext.SubmitChanges() +38

What's the data type of your id field?
I just ran the following code against a dummy database that has data in it where my key field is of type int:
using (var db = new dbDataContext())
{
var x = db.items.Single(i => i.fld_key == 1);
db.items.DeleteOnSubmit(x);
db.SubmitChanges();
var y = (from i in db.items
where i.fld_key == 2
select i).First();
db.items.DeleteOnSubmit(y);
db.SubmitChanges();
}
I had no problems with either, and checking the database, items 1 and 2 were both deleted successfully.
Are you attempting to delete an item that doesn't exist in the second code block - as you deleted it with the first. This would mean that y is actually null and you're attempting to DeleteOnSubmit passing null which will give you an invalid cast as you can't cast null.
Try attempting to delete a different item in your second code block, I would imagine it would work in that case.
Change .Single to .SingleOrDefault and wrap your delete lines to check for null:
if(null != x)
{
db.items.DeleteOnSubmit(x);
db.SubmitChanges();
}
I would wager this will fix the problem you're seeing.
Edit Looking at the exception you've posted, I'm wondering if you changed the data type of your table to int after you generated your dbml. Reload the table into your dbml and see if that fixes the issue. Either that, or the issue is the other way around and you changed it to VarChar/NVarChar and haven't updated your dbml.
I would definitely take a look at your entities and make sure that the data types match those in your data table in your underlying database.

Have you changed the datatypes in your database lately? (Maybe those types currently differ from your generated classes)
Similarly, if you're using a stored procedure for deleting items, make sure the parameter types match up to your generated types.

Related

Saving and retrieving JSONB columns using SqlKata

I'm manually serializing objects to and from strings and trying to store them in a Postgres database using SqlKata:
var obj = new { Id = 3, JsonB = "{a: 5}" };
dbInstance.Query("TableName").InsertAsync(obj);
The column JsonB is of type jsonb. When I attempt to do it this way, I get the following error:
Exception data:
Severity: ERROR
SqlState: 42804
MessageText: column "DataField" is of type jsonb but expression is of type text
I don't see anywhere in the documentation nor the code to where I can cast types on insert, and am at a loss on how to make it so that I can save JSON strings/objects to the appropriate fields.
I have same problem. For inserts where json column is present in table switched to use NpgsqlCommand and Npgsql.Json.NET library for serializing objects on the fly. Not SqlKata but working:
Product prod;
using (NpgsqlCommand command =
new NpgsqlCommand(#$"INSERT INTO products (json_col) VALUES (#json_col)", connection))
{
command.Parameters.Add(
new NpgsqlParameter("json_col", NpgsqlTypes.NpgsqlDbType.Json) { Value = prod });
await command.ExecuteNonQueryAsync();
}
I don't know SqlKata, but indeed you need to tell Npgsql that you're sending a json (or jsonb) type, since strings are mapped to the PostgreSQL text type by default. PostgreSQL is quite type-strict and will not implicitly cast between most types - you will need to find out how to set NpgsqlDbType via SqlKata.

Delete multiple objects with a single query (or in transaction)

I'm using Dapper with Dapper-Extensions. I'm currently deleting all the objects one-by-one:
dbConnection.Delete<MyObj>(data);
This is bad not only for performance, but also because if a delete fails I would like to rollback the entire operation. Is there a way to perform a "massive" delete, for example passing a list of objects instead of data?
You may pass IPredicate to delete multiple records based on condition (WHERE clause) in one go.
If you simply pass empty IPredicate, all records from the table will be deleted.
Following function handles both the cases:
protected void DeleteBy(IPredicate where)
{//If 'where' is null, this method will delete all rows from the table.
if(where == null)
where = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };//Send empty predicateGroup to delete all records.
var result = connection.Delete<TPoco>(predicate, ......);
}
In above code, TPoco is your POCO type which is mapped to database table you are talking about.
You can build the predicate something like below:
var predicateGroup = new PredicateGroup { Operator = GroupOperator.And, Predicates = new List<IPredicate>() };
if(!string.IsNullOrEmpty(filterValue))
predicateGroup.Predicates.Add(Predicates.Field<MyPoco>(x => x.MyProperty, Operator.Eq, PredicateGroup));
Transaction is different thing. You can put all your current code in transaction. You can put my code in transaction as well. With my code, transaction does not make much difference though; although it is recommended to always use transactions.
About passing list of objects, I do not see any way. Following are two extension methods of Dapper Extensions for deleting the record:
public static bool Delete<T>(this IDbConnection connection, object predicate, IDbTransaction transaction = null, int? commandTimeout = default(int?)) where T : class;
public static bool Delete<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = default(int?)) where T : class;
None of it accepts list of objects. One accept predicate and other accepts single object.

Generate sql query by anorm, with all nulls except one

I developing web application with play framework 2.3.8 and scala, with complex architecture on backend and front-end side. As backend we use MS SQL, with many stored procedures, and called it by anorm. And here one of the problems.
I need to update some fields in database. The front end calls play framework, and recive name of the field, and value. Then I parse, field name, and then I need to generate SQL Query for update field. I need assign null, for all parameters, except recived parameter. I try to do it like that:
def updateCensusPaperXX(name: String, value: String, user: User) = {
DB.withConnection { implicit c =>
try {
var sqlstring = "Execute [ScXX].[updateCensusPaperXX] {login}, {domain}"
val params = List(
"fieldName1",
"fieldName2",
...,
"fieldNameXX"
)
for (p <- params){
sqlstring += ", "
if (name.endsWith(p))
sqlstring += value
else
sqlstring += "null"
}
SQL(sqlstring)
.on(
"login" -> user.login,
"domain" -> user.domain,
).execute()
} catch {
case e: Throwable => Logger.error("update CensusPaper04 error", e)
}
}
}
But actually that doesn't work in all cases. For example, when I try to save string, it give's me an error like:
com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near 'some phrase'
What is the best way to generate sql query using anorm with all nulls except one?
The reason this is happening is because when you write the string value directly into the SQL statement, it needs to be quoted. One way to solve this would be to determine which of the fields are strings and add conditional logic to determine whether to quote the value. This is probably not the best way to go about it. As a general rule, you should be using named parameters rather than building a string to with the parameter values. This has a few of benefits:
It will probably be easier for you to diagnose issues because you will get more sensible error messages back at runtime.
It protects against the possibility of SQL injection.
You get the usual performance benefit of reusing the prepared statement although this might not amount to much in the case of stored procedure invocation.
What this means is that you should treat your list of fields as named parameters as you do with user and domain. This can be accomplished with some minor changes to your code above. First, you can build your SQL statement as follows:
val params = List(
"fieldName1",
"fieldName2",
...,
"fieldNameXX"
)
val sqlString = "Execute [ScXX].[updateCensusPaperXX] {login}, {domain}," +
params.map("{" + _ + "}").mkString{","}
What is happening above is that you don't need to insert the values directly, so you can just build the string by adding the list of parameters to the end of your query string.
Then you can go ahead and start building your parameter list. Note, the parameters to the on method of SQL is a vararg list of NamedParameter. Basically, we need to create Seq of NamedParameters that covers "login", "domain" and the list of fields you are populating. Something like the following should work:
val userDomainParams: Seq[NamedParameter] = (("login",user.login),("domain",user.domain))
val additionalParams = params.map(p =>
if (name.endsWith(p))
NamedParameter(p, value)
else
NamedParameter(p, None)
).toSeq
val fullParams = userDomainParams ++ additionalParams
// At this point you can execute as follows
SQL(sqlString).on(fullParams:_*).execute()
What is happening here is that you building the list of parameters and then using the splat operator :_* to expand the sequence into the varargs needed as arguments for the on method. Note that the None used in the NamedParameter above is converted into a jdbc NULL by Anorm.
This takes care of the issue related to strings because you are no longer writing the string directly into the query and it has the added benefit eliminating other issues related with writing the SQL string rather than using parameters.

How to read a `geography` column by `SqlDataReader`?

I have a SQL Server 2008 database with a geography column which is generated by System.Data.Entity.Spatial.DbGeography in Entity Framework 6.0.0-alpha3.
Now I need to read that column with a SqlDataReader. But I have no idea how to do this. Using the old context is not an option. I tried to cast it as DbGeography:
Location = (DbGeography)reader.GetValue(index)
But I get this error:
Unable to cast object of type 'Microsoft.SqlServer.Types.SqlGeography'
to type 'System.Data.Entity.Spatial.DbGeography'
Do you have any suggestion?
Well, it was simple. I was just confused. But instead of deleting the question, I'll post the answer to others who they have the same question.
// read the value as dynamic:
dynamic temp = reader.GetValue(index);
// the temp contains Lat and Long properties:
var text = string.Format("POINT({0:R} {1:R})", temp.Long, temp.Lat);
// the temp also contains the STSrid as coordinate system id:
var srid = temp.STSrid.Value;
// the rest is really simple:
Location = System.Data.Entity.Spatial.DbGeography.PointFromText(text, srid);
If your geography is a point, you can use:
SELECT MyColumn.Lat, MyColumn.Long ...
reader.GetDouble(0);
reader.GetDouble(1);

checking null return from database using getSingleResult of entity manager

I have to retrieve the db entry. I know the query I have written returns only one row. Therefor I am using getSingleResult. However when the query returns a null, I am not able to catch it. How do I solve this?
Here is the piece of code I have
try {
result = em.createQuery("SELECT d FROM fields d WHERE d.fieldID = :fieldID", Field.class)
.setParameter("fieldID", fieldID)
.getSingleResult();
//manual null check only seems to work. But it seems tedious to check every DB column for null value :(
if((result.getValsText() == null)){
result = new Field();
result.setValText("empty");
}
} catch (NoResultException e) {
result = new Field();
result.setValText("empty");
}
Please advise.
Thanks
For non-Id attributes you can either
send a COUNT query before your regular query or better
call getResultList() on your Query
getResultList will return an empty list if no results have been found. To get your single result, check whether the list is empty and call get(0) on it.
If the attribute is the #Id attribute, call entityManager.find(MyEntity.class, id); - find returns null if no results have been found and a single instance of your entity if it has been found.
EDIT- the last option is preferable for reasons of perfomance and readability. Use the second option (the list) where you cannot use the last one.

Resources