EF6 IQueryable dynamic linq Where(predicate, values) with DbFunctions.TruncateTime - sql-server

In my simplified example i have an object with following properties:
Name (string)
BirthDateTimeStamp (datetime)
I need to ba able to build dynamic queries in the following way
var predicate = "Name = #0";
var values = new object[]{"Ed"};
myIQueryableDataSource.Where(predicate, values)
This work good. Now i want to compare my datetime
var predicate = "BirthDateTimeStamp >= #0";
var values = new object[]{someDateTime};
This works good also. But what i actually want to do when comparing the datetimes and this issue shows itself better when doing the equals is comparing just on date.
var predicate = "BirthDateTimeStamp.Date >= #0";
This is not possible since the Date property is not recognized by EF to SQL server
var predicate = "System.Data.Entity.DbFunctions.TruncateTime(BirthDateTimeStamp) >= #0";
This is also not working since i can only access my object properties in the predicate.
How can i solve this in this way so the predicate stays in the string format. This code is just a part of a big existing parser for my queries and connat be completely rewritten.

See https://stackoverflow.com/a/26451213/525788
System.Linq.Dynamic is parsing the expression that you give as C# but does not recognize the class DbFunctions. However you can patch in DbFunctions as a predefined type:
var type = typeof( DynamicQueryable ).Assembly.GetType( "System.Linq.Dynamic.ExpressionParser" );
FieldInfo field = type.GetField( "predefinedTypes", BindingFlags.Static | BindingFlags.NonPublic );
Type[] predefinedTypes = (Type[])field.GetValue( null );
Array.Resize( ref predefinedTypes, predefinedTypes.Length + 1 );
predefinedTypes[ predefinedTypes.Length - 1 ] = typeof( DbFunctions );
field.SetValue( null, predefinedTypes );
Then you can use
var predicate = "DbFunctions.TruncateTime(BirthDateTimeStamp) >= #0";

Answer given by #RockResolve did work, but is kind a hack. Linq Dynamics provide functionality to add custom functions
public class CustomTypeProvider: IDynamicLinkCustomTypeProvider
{
public HashSet<Type> GetCustomTypes()
{
HashSet<Type> types = new HashSet<Type>();
// adding custom types
types.Add(typeof(DbFunctions));
return types;
}
}
// use below line to add this to linq
System.Linq.Dynamics.GlobalConfig.CustomTypeProvider = new CustomTypeProvier();

Related

Criteria API find Key-Value Pairs in Map

I try to use the Criteria API to create a dynamic JPA-Query. I need to find a key-value pair inside a map of the object.
The Object looks similar to the following one.
public class item {
private UUID id;
#Column(name = "properties", columnDefinition = "nvarchar")
private Map<String, Object> properties;
}
I thought I could use the MapJoin join or joinMap:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Item> criteriaQuery = cb.createQuery(Item.class);
Root<Item> itemRoot = criteriaQuery.from(Item.class);
criteriaQuery.select(itemRoot);
Join<String, Object> properties = itemRoot.join("properties");
// or
//MapJoin<Item, String, Object> properties = itemRoot.joinMap("properties");
Predicate pre1 = cb.equal(properties.get(ITEM_PROPERTY_1), "123");
Predicate pre2 = cb.equal(properties.get(ITEM_PROPERTY_2), "456");
Predicate propertiesPredicate = cb.and(pre1, pre2);
criteriaQuery.where(propertiesPredicate);
Item item = em.createQuery(criteriaQuery).getSingleResult();
But I've read that this is only for associations.
On the join i get an:
IllegalArgumentException: Requested attribute was not a map.
So could sb explain to me, how I will be able to find a key-value pair in a map with the Criteria API?
Edit: I am not able to change anything in the DB.
So I need to guess a little bit because you didn't show us your DB Table, that's why I answer a little bit more freely.
And as a disclaimer: it might be easier and it would be more efficient to query a real table instead of an serialized object/json.
But I would do it this way:
The Table in MSSQL:
create table ${schema}.myTable
(
id bigint identity
constraint PK_myStuff
primary key,
properties nvarchar(max) not null
) go
The Java entity (draft):
public class Item extends AbstractPersistable<...> {
#Column(name = "properties", columnDefinition = "nvarchar")
private String properties;
}
The Java Specification:
protected Specification<Item> customFilter(String filterArg) {
return (root, query, cb) ->
cb.like(root.get(Item_.properties), filterArg);
}
This way your query searches the properties for a string pattern.
Info:
https://vladmihalcea.com/sql-server-json-hibernate/

How to search for empty strings in a text field with Entity Framework?

I'd like to know how can I search for empty strings when I'm using a text type field with Entity Framework.
I've looked the SQL query that Entity is generating and It's using LIKE to compare because It's searching in a text type field, so when I use .Equals(""), == "", == string.Empty, .Contains(""), .Contains(string.Empty), and everything else, It's returning all results because it sql query is like '' and the == command throws exception because It uses the = command that is not valid with text type field.
When I try to use .Equals(null), .Contains(null), == null, It returns nothing, because It is generating FIELD ISNULL command.
I already tried the .Lenght == 0 but It throws an exception...
This works for me:
public class POCO
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
static void Main(string[] args)
{
var pocos = new List<POCO>
{
new POCO { Id = 1, Name = "John", Description = "basic" },
new POCO { Id = 2, Name = "Jane", Description = "" },
new POCO { Id = 3, Name = "Joey", Description = string.Empty }
};
pocos.Where(x => x.Description == string.Empty)
.ToList()
.ForEach(x => Console.WriteLine($"{x.Id} {x.Name} {x.Description}"));
}
However the issue MAY BE that your T4 generated object is not fully realized with data you can use, if you are using Entity Framework. EG the translation from the database is not populating objects to interrogate correctly. I would just do an operation like this to see:
using (var context = new YOURCONTEXTNAME())
{
var persons = context.YOURDATABASEOBJECT.ToList();
persons.ForEach(x => Console.WriteLine($"{x.COLUMNINQUESTION}"));
}
If you are successfully having data in it, it should be retrieved. I would not use text if possible. Use a varchar(max) nvarchar(max) xml, whatever text will be deprecated eventually and is bad form so to speak to continue using at this point.
EDIT
Okay I see, the answer is you cannot interogate the object until it is fully realized when it is text. I did a test on my local database and created a context and tested it and sure enough you cannot do a '== string.empty', '== ""', or 'String.IsNullOrEmpty()' on a text. However you can do it once the object is materialized in a realized object. EG:
// Won't work as context does not understand type
//var persons = context.tePersons.Where(x => x.Description == string.Empty).ToList();
//Works fine as transformation got the object translated to a string in .NET
var start = context.tePersons.ToList();
var persons = start.Where(x => x.Description == String.Empty).ToList();
This poses a problem obviously as you need to get ALL your data potentially before performing a predicate. Not the best means by any measure. You could do a sql object for this instead then to do a function, proc, or view to change this.

How can I set the value and the datatype when creating a new SqlParameter?

I have my code setting up a parameter for QuestionsCount and then for Questions:
parameterList.Add(new SqlParameter("#QuestionsCount", chunkSize));
var p = new SqlParameter("#Questions", questions);
p.TypeName = "dbo.QuestionList";
parameterList.Add(p);
Is there a way that I can combine the last three lines and create a new SqlParameter with the typeName. I was looking at the definitions but cannot find one that takes the value (questions) and the TypeName "dbo.QuestionList".
There are quite a few overloads for the constructor for SqlParameter, some of which allow you to specify a datatype and optionally a length:
var p = new SqlParameter("#Questions", SqlDbType.VarChar, 100);
p.Value = ":.......";
parameterList.Add(p);
Check out the MSDN documentation on SqlParameter for details.
One method is with an initializer to supply property values not available in the constructor overloads:
parameterList.Add(new SqlParameter( "#Questions", SqlDbType.Structured ) { TypeName = "dbo.QuestionList", Value = questions });

SqlDateTimeOverflow exception with nullable DateTime fields with Dapper update

I have a db table with several DateTime fields with null values. These are mapped to nullable DateTimes in my class.
If I try to perform an update with Dapper, within my data layer:
using (IDbConnection cnn = new SqlConnection(DB.getConString()))
{
cnn.Open();
return cnn.Execute((this.OptionID == 0 ? _insertSQL : _updateSQL), this);
}
I get a SqlDateTimeOverflow exception (because the DateTime field is '01/01/0001 00:00:00' rather than null.
Is the only way around this to specify each parameter individually and switch the value to null like this:
using (IDbConnection cnn = new SqlConnection(DB.getConString()))
{
cnn.Open();
return cnn.Execute("UPDATE MyTable SET MyDateField = #MyDateField", new {MyDateField = (MyDateField.HasValue? MyDateField : Null), etc etc... );
I have about 50 fields in the table so this would be quite a bit of code, plus there is an INSERT method to update similarly too. Is there an easier syntax I am missing?
The issue here is that 01/01/0001 00:00:00 is not a "null value"; if you had used DateTime? I suspect it would have worked fine. However, I also have to recognize that DateTime.MinValue has often (mainly due to .NET 1.1 lacking nullable structs) been used to represent a null value. My preferred suggestion here would be to simply use DateTime?. The min-value map is a bit complicated as we might also consider whether that should be automatically mapped instead to the sql min-value (January 1, 1753).
Re the update statement - maybe add an extension method to map between min-value and null?
public static DateTime? NullIfZero(this DateTime when) {
return when == DateTime.MinValue ? (DateTime?)null : when;
}
and use:
new { MyDateField = MyDateField.NullIfZero() }
but again, if MyDateField was DateTime?, you could just use:
new { MyDateField }

SQLiteDataReader GetFieldType() returns Int64, but then fails on GetInt64() - is this a bug or feature?

When reading from an SQLiteDataReader I'm experiencing some odd behaviour whereby GetFieldType(0) returns typeof(Int64), GetValue(0) returns an Int64, but GetInt64(0) fails with an System.InvalidCastException exception.
It has taken me a rather long time to reproduce this behaviour:
using System;
using System.Data.SQLite;
using NUnit.Framework;
namespace Test
{
[TestFixture]
public class SQLiteType
{
[Test]
public void A()
{
var sqlConnection = new SQLiteConnection("Data Source=:memory:;Version=3;");
sqlConnection.Open();
var create = sqlConnection.CreateCommand();
create.CommandText = "CREATE TABLE FOO (x INTEGER)";
create.ExecuteNonQuery();
var insert = sqlConnection.CreateCommand();
insert.CommandText = "INSERT INTO FOO VALUES (?)";
var param = insert.CreateParameter();
param.Value = new TimeSpan(0); // NOTE INSERTING TIMESPAN DIRECTLY instead of .Ticks
insert.Parameters.Add(param);
insert.ExecuteNonQuery();
var select = sqlConnection.CreateCommand();
select.CommandText = "SELECT x FROM FOO";
var dr = select.ExecuteReader();
while (dr.Read())
{
var valueObject = dr.GetValue(0);
Assert.AreEqual(typeof (Int64), valueObject.GetType());
var valueType = dr.GetFieldType(0);
Assert.AreEqual(typeof (Int64), valueType);
var value = dr.GetInt64(0); // throws System.InvalidCastException
}
}
}
}
It seems to occur when the row is created by inserting a TimeSpan value directly into an INTEGER column (instead of e.g. TimeSpan.Ticks which might be more meaningful). Despite this, the datareader is still telling me that column is an Int64.
I'm not exactly sure what the contract is for SQLiteDataReader but I had previously assumed that if GetFieldType() returns a typeof(Int64), then GetInt64() should not fail. Perhaps this is not the case? (It seems quite odd that GetValue() still returns an Int64) Maybe it is an artifact of SQLite's unique dynamic typing system.
Certainly it is not hard to avoid, but for pedagogical reasons I am just curious why this is happening?
The root cause may have to do with how types are handled with SQLite:
http://www.sqlite.org/datatype3.html#affinity
Even then, this looks like a bug to me; if:
dr.GetValue(0).GetType() == typeof(System.Int64)
then it should certainly follow that dr.GetInt64(0) doesn't throw an exception. I would send an email to sqlite-users#sqlite.org as described here: http://www.sqlite.org/src/wiki?name=Bug+Reports
Please note though that if you replace:
param.Value = new TimeSpan(0);
with
param.Value = new TimeSpan(0).Ticks;
then
var value = dr.GetInt64(0);
works fine. I'm bringing this up because I'm not sure there is any conversion assumption to make when you assign that TimeSpan. For instance, there is no implicit or explicit conversion from TimeSpan to long.

Resources