I am starting to work with Nhibernate and Oracle for a project.
The database is Oracle 9.2 and I can't change the schema or anything.
I am using NH3.0 and the Oracle.DataAccess.dll ver 2.111.7.20.
So far I've mapped a couple of table and have done some queries. Everything works pretty well.
I've bumped into a problem now and I don't know how to fix it.
The company who's designed the database thought it was a good idea to create all the alphanumeric fields as CHAR instead of VARCHAR or VARCHAR2.
I've mapped all these columns as String and my classes have String fields defined.
Later on I was trying to load an entity by its primary key, defined as CHAR(10) in Oracle.
The key I was trying to load is only 7 characters long
EG: 'CI00252'
Apparently my entity can't be loaded.
Profiling the query with NHProf I can see that my query is fine and if I try to execute it in Oracle Sql-Developer I get the resultset.
I can only manage to make it work if I pad my string like this 'CI00252 '.
Considering that most of the fields defined on the database are CHAR, it is impossible for me to pad everything before executing the query.
What can I do to fix this problem?
PS: I've seen some other people with the same problem here but I couldn't find any appropriate answer.
UPDATE:
I was reading a blog and this guy had a similar problem with another data-type.I've tried to adpapt the code
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.StringFixedLength);
return types;
}
}
**
and, apparently, everything is working but ... I don't know why.
using System;
using System.Data;
using NHibernate;
using NHibernate.SqlTypes;
using NHibernate.UserTypes;
namespace ConsoleOracleNhibernate.OracleTypes
{
public class CharUserType : IUserType
{
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
string resultString = (string)NHibernateUtil.String.NullSafeGet(rs, names[0]);
if (resultString != null)
return resultString.Trim();
return null;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if (value == null)
{
NHibernateUtil.String.NullSafeSet(cmd, null, index);
return;
}
value = ((String)value).Trim();
NHibernateUtil.String.NullSafeSet(cmd, value, index);
}
public object DeepCopy(object value)
{
if (value == null) return null;
return string.Copy((String)value);
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public SqlType[] SqlTypes
{
get
{
SqlType[] types = new SqlType[1];
types[0] = new SqlType(DbType.StringFixedLength);
return types;
}
}
public Type ReturnedType
{
get { return typeof(String); }
}
public bool IsMutable
{
get { return false; }
}
public new bool Equals(object x, object y)
{
if (x == null || y == null) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
}
}
and my mapping:
<key-property name="CustomerCode" column="ANCOCO" type="ConsoleOracleNhibernate.OracleTypes.CharUserType, ConsoleOracleNhibernate" length="10"></key-property>
Is there anyone here who can try to help me to understand what's happening?
My answer is in the update of the question.
Related
In short I wanted to create a custom IUserType to represent IPAddress from .NET (as an inet type in postgresql) and to be able to query it with custom functions via HQL and Linq. I have trouble with implementing a custom function for Linq.
What I have thus far:
A) I am able to map it:
public class SomeEntityMapper : ClassMap<SomeEntity> {
public SomeEntityMapper() {
...
Map(x => x.IpAddressField)
.CustomSqlType("inet")
.CustomType<IPAddressUserType>()
...
}
}
IUserType implementation below:
[Serializable]
public class IPAddressUserType : IUserType
{
public new bool Equals(object x, object y)
{
if (x == null && y == null)
return true;
if (x == null || y == null)
return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
if (x == null)
return 0;
return x.GetHashCode();
}
public object NullSafeGet(DbDataReader rs, string[] names, ISessionImplementor session, object owner)
{
if (names.Length == 0)
throw new InvalidOperationException("Expected at least 1 column");
if (rs.IsDBNull(rs.GetOrdinal(names[0])))
return null;
object value = rs[names[0]];
return value;
}
public void NullSafeSet(DbCommand cmd, object value, int index, ISessionImplementor session)
{
NpgsqlParameter parameter = (NpgsqlParameter) cmd.Parameters[index];
parameter.NpgsqlDbType = NpgsqlTypes.NpgsqlDbType.Inet;
if (value == null)
{
parameter.Value = DBNull.Value;
}
else
{
parameter.Value = value;
}
}
public object DeepCopy(object value)
{
if (value == null)
return null;
IPAddress copy = IPAddress.Parse(value.ToString());
return copy;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
if (cached == null)
return null;
if (IPAddress.TryParse((string)cached, out var address))
{
return address;
}
return null;
}
public object Disassemble(object value)
{
if (value == null)
return null;
return value.ToString();
}
public SqlType[] SqlTypes => new SqlType[] { new NpgsqlSqlType(DbType.String, NpgsqlTypes.NpgsqlDbType.Inet), };
public Type ReturnedType => typeof(IPAddress);
public bool IsMutable => false;
}
public class NpgsqlSqlType : SqlType
{
public NpgsqlDbType NpgDbType { get; }
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType)
: base(dbType)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, int length)
: base(dbType, length)
{
NpgDbType = npgDbType;
}
public NpgsqlSqlType(DbType dbType, NpgsqlDbType npgDbType, byte precision, byte scale)
: base(dbType, precision, scale)
{
NpgDbType = npgDbType;
}
}
}
B) I am able to query it using HQL and a custom function (inet_equals) I implemented.
using(var session = _factory.OpenSession()) {
var q = session.CreateQuery("from SomeEntity as s WHERE inet_equals(s.IpAddressField, :ip)");
q.SetParameter("ip", IPAddress.Parse("4.3.2.1"), NHibernateUtil.Custom(typeof(IPAddressUserType)));
}
Custom Postgresql Dialect extending the HQL functions implementation below:
public class CustomPostgresqlDialect : PostgreSQL83Dialect
{
public CustomPostgresqlDialect()
{
RegisterFunction("inet_equals", new SQLFunctionTemplate(NHibernateUtil.Boolean, "(?1::inet = ?2::inet)"));
}
}
C) I want to be able to query it using LINQ like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(IPAddress.Parse("4.3.2.1")));
}
Custom LINQ Generator for NHibernate provider below:
public static class InetExtensions
{
public static bool InetEquals(this IPAddress value, IPAddress other)
{
throw new NotSupportedException();
}
}
public class InetGenerator : BaseHqlGeneratorForMethod
{
public InetGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => InetExtensions.InetEquals(default(IPAddress), default(IPAddress)))
};
}
public override HqlTreeNode BuildHql(MethodInfo method, System.Linq.Expressions.Expression targetObject, ReadOnlyCollection<System.Linq.Expressions.Expression> arguments,
HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
HqlExpression lhs = visitor.Visit(arguments[0]).AsExpression();
HqlExpression rhs = visitor.Visit(arguments[1]).AsExpression();
return treeBuilder.BooleanMethodCall(
"inet_equals",
new[]
{
lhs,
rhs
}
);
}
}
public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new InetGenerator());
}
}
Unfortunately when using LINQ I get this exception:
HibernateException: Could not determine a type for class: System.Net.IPAddress
Interestingly, this is the exact same error I get if I omit the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter inside the HQL query.
Which leads me to believe I am on the right track, but I can't figure out what I may be missing exactly. I assume I need to inform NHibernate inside the Generator that this is a custom UserType (just like I did with the HQL query via the NHibernateUtil.Custom(typeof(IPAddressUserType)) parameter.
I found the solution.
To hint NHibernate to use the proper UserType use: MappedAs extension method, like so:
using(var session = _factory.OpenSession()) {
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(
IPAddress.Parse("4.3.2.1").MappedAs(NHibernateUtil.Custom(typeof(IPAddressUserType))
);
}
The year is 2019 and I'm still stuck on version 3.3.2.GA of NHibernate and the MappedAs extension method exists only on version 4.x onwards.
My scenario was the need to pass a large string as a parameter to a supported method in an HqlGeneratorForMethod.
I created the following class to store any large string to use as a parameter of any method throughout the application:
public class StringClob
{
public StringClob()
{
}
public StringClob(string value)
{
Value = value;
}
public virtual string Value { get; protected set; }
}
In order to link the NHibernateUtil.StringClob type with the string value I had the idea to create a mapping for my class without informing table mapping just the property (I use FluentNHibernate to map class):
public class StringClobMap : ClassMap<StringClob>
{
public StringClobMap()
{
Id(x => x.Value, "VALUE").CustomType("StringClob").CustomSqlType("VARCHAR(MAX)").Length(int.MaxValue / 2);
}
}
And now hypothetically following the example of #krdx the usage would look like this:
using(var session = _factory.OpenSession())
{
var q = session.Query<SomeEntity>()
.Where(s => s.IpAddressField.InetEquals(new StringClob("<large_string_here>")));
// ...
}
Therefore by passing the StringClob class as a parameter, NHibernate gets the custom type defined in the mapping.
I hope I helped those who still use NHibernate 3.3.2.GA.
I am trying to figure out why I am getting an invalid cast exception with nHibernate with the following code:
AutoMap.Source(new TypeSource(recordDescriptors))
.Conventions.Add(new EncryptedStringConvention());
.
[AttributeUsage(AttributeTargets.Property)]
public class EncryptedDbString : Attribute { }
.
public class EncryptedStringConvention : IPropertyConvention {
public void Apply(IPropertyInstance instance) {
if (!instance.Property.MemberInfo.IsDefined(typeof(EncryptedDbString), false))
return;
var propertyType = instance.Property.PropertyType;
var generic = typeof(EncryptedStringType<>);
var specific = generic.MakeGenericType(propertyType);
instance.CustomType(specific);
}
}
.
[Serializable]
public class EncryptedStringType<T> : PrimitiveType
{
const int MaxStringLen = 1000000000;
public EncryptedStringType() : this(new StringSqlType(MaxStringLen)) { }
public EncryptedStringType(SqlType sqlType) : base(sqlType) { }
public override string Name {
get { return typeof(T).Name; }
}
public override Type ReturnedClass {
get { return typeof(T); }
}
public override Type PrimitiveClass {
get { return typeof(T); }
}
public override object DefaultValue {
get { return default(T); }
}
public override object Get(IDataReader rs, string name) {
return Get(rs, rs.GetOrdinal(name));
}
public override void Set(IDbCommand cmd, object value, int index) {
if (cmd == null) throw new ArgumentNullException("cmd");
if (value == null) {
((IDataParameter)cmd.Parameters[index]).Value = null;
}
else {
((IDataParameter)cmd.Parameters[index]).Value = Encryptor.EncryptString((string)value);
}
}
public override object Get(IDataReader rs, int index) {
if (rs == null) throw new ArgumentNullException("rs");
var encrypted = rs[index] as string;
if (encrypted == null) return null;
return Encryptor.DecryptString(encrypted);
}
public override object FromStringValue(string xml) {
// i don't think this method actually gets called for string (i.e. non-binary) storage
throw new NotImplementedException();
}
public override string ObjectToSQLString(object value, Dialect dialect) {
// i don't think this method actually gets called for string (i.e. non-binary) storage
throw new NotImplementedException();
}
}
POCO that works:
public class someclass {
public virtual string id {get;set;}
[EncryptedDbString]
public virtual string abc {get;set;}
}
POCO that fails:
public class otherclass {
public virtual string id {get;set;}
[EncryptedDbString]
public virtual Guid def {get;set;}
}
This is all automapped with Fluent.
Both the Guid type and string type are nvarchar(500) in the SQL database.
As mentioned, the first POCO works fine and encrypts/decrypts as expected, but the second POCO fails, and this is what I see in my logs:
NHibernate.Tuple.Entity.PocoEntityTuplizer.SetPropertyValuesWithOptimizer(Object entity, Object[] values)
{"Invalid Cast (check your mapping for property type mismatches); setter of otherclass"}
Note that the second POCO object works fine with nHib if I remove the EncryptedDbString attibute, i.e. it has no problems saving the Guid to a nvarchar.
Obviously the issue here is that it's a Guid as the string case works, but I do want it kept as a Guid not a string in the code, and I can't see the point of failure here.
Seems like I'm missing something small. I guess I'm missing something with the generics, but I've only found code snippets out there rather than a full example like this.
EDIT:
ok, so i figured out it i think it was because the
Get(IDataReader rs, int index)
was not returning a Guid object.
so I guess you can serialize/deserialize in the EncryptedStringType Get/Set methods, e.g. in the Get() you could change to:
if (typeof(T) == typeof(string))
return decrypted;
var obj = JsonConvert.DeserializeObject(decrypted);
return obj;
but that seems horrible, especially if you have existing data to migrate.
i don't want to store stuff as binary either, as the team want to be able to check/test/audit manually via SQL which columns are encrypted (which is obvious with text, but not binary).
a string backing field in my POCO that converts the Guid to a string and back again via simple get/set methods might be the best option, but I have no idea how to do that with automapping across the solution or how messy it is?
Having slept, I think i've been thinking about this the wrong way.
I've now realised that my reticence to store json in the database was driven by the fact that I am storing string-biased objects - i.e. things that naturally convert to text fields, as opposed to full objects. myGuid.ToString() gives you a guid string, myDateTime.ToString() gives you a datetime string etc.
So given that object serialisation per se isn't needed in my case, but rather just conversion to a string, Andrew's suggestion seems like a good solution.
Updated code:
public override void Set(IDbCommand cmd, object value, int index) {
var prm = ((IDataParameter) cmd.Parameters[index]);
if (cmd == null) throw new ArgumentNullException("cmd");
if (value == null) {
prm.Value = null;
return;
}
string str;
try {
// guid becomes a simple guid string, datetime becomes a simple
// datetime string etc. (ymmv per type)
// note that it will use the currentculture by
// default - which is what we want for a datetime anyway
str = TypeDescriptor.GetConverter(typeof(T)).ConvertToString(value);
}
catch (NotSupportedException) {
throw new NotSupportedException("Unconvertible type " + typeof(T) + " with EncryptedDbString attribute");
}
prm.Value = Encryptor.EncryptString(str);
}
public override object Get(IDataReader rs, int index) {
if (rs == null) throw new ArgumentNullException("rs");
var encrypted = rs[index] as string;
if (encrypted == null) return null;
var decrypted = Encryptor.DecryptString(encrypted);
object obj;
try {
obj = (T)TypeDescriptor.GetConverter(typeof(T)).ConvertFromString(decrypted);
}
catch (NotSupportedException) {
throw new NotSupportedException("Unconvertible type " + typeof(T) + " with EncryptedDbString attribute");
}
catch (FormatException) {
// consideration - this will log the unencrypted text
throw new FormatException(string.Format("Cannot convert string {0} to type {1}", decrypted, typeof(T)));
}
return obj;
}
An improvement would be for the EncryptedStringConvention to have the Accept() method added to pre-check that all the types marked with the EncryptedDbString attribute were convertible. Possibly we could use Convert() and type is IConvertible instead, but I'll leave it as, enough time spent!
I have a database which is accessed by multiple systems. Ours reads a table using Fluent Nhibernate and Automapping.
One of the columns is a string representing an enum. In most installations this is fine, but every now and then another system will persist an invalid value. How can I catch these invalid values and either convert or ignore them?
Example:
Database Table:
ID MyEnum
0 "One"
1 "Two"
2 "Invalid"
MyEnum
{
One,
Two
}
I'm already using IAutoMappingOverride with my model, is there anyway to pass a function to the mapper to do validation/conversion (say I wanted to convert any unknowns to MyEnum.One)?
Or is there a simpler way that I'm missing?
Where I got to was using a combination of #Firo's answer and the answers here and here
class SafeEnumType<T> : ImmutableUserType where T : struct
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
T value;
if (Enum.TryParse((string)rs[names[0]], out value))
return value;
else
return default(T);
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
NHibernateUtil.String.NullSafeSet(cmd, value.ToString(), index);
}
public override Type ReturnedType
{
get { return typeof(T); }
}
public override SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.GetString(100) }; }
}
}
and adding
mapping.Map(x => x.Type).CustomType(typeof(SafeEnumType<EventType>));
to my AutomapOverride.Override() fn
In NHibernate there is IUserType which lets you define custom conversion code. here is a base class of mine which you can use because enums are immutable types
code
class SpecialEnumUserType : ImmutableUserType
{
public override object NullSafeGet(IDataReader rs, string[] names, object owner)
{
TheSpecialEnum value;
if (Enum.TryParse<TheSpecialEnum>((string)rs[names[0]], out value))
return value;
else
return default(TheSpecialEnum);
}
public override void NullSafeSet(IDbCommand cmd, object value, int index)
{
NHibernateUtil.String.NullSafeSet(cmd, value.ToString(), index);
}
public override Type ReturnedType
{
get { return typeof(TheSpecialEnum); }
}
public override SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.GetString(50) }; }
}
}
and convention to apply them to every enum property
class EnumHandlingConvention : IPropertyConvention
{
public void Apply(IPropertyInstance instance)
{
if (instance.Type == typeof(TheSpecialEnum))
{
instance.CustomType<SpecialEnumUserType>();
}
}
}
In my project I use a IUserType (BooleanM1) that handles boolean values and writes -1 for true and 0 for false values to the database. So far everything works well. The mapping looks like
this:
<property name="Active" column="ACTIVE" type="Core.Persistence.NH.Types.BooleanM1,Core.Test"/>
So if I do a query like the following
var pList = Session.Query<Test>().Where( c => c.Active ).ToList();
an exception is thrown:
NHibernate.QueryException: Unable to render boolean literal value [.Where[Core.Test.Domain.Test]
(NHibernate.Linq.NhQueryable`1[Core.Test.Domain.Test], Quote((c, ) => (c.Active)), )]
---> System.InvalidCastException: Das Objekt des Typs "NHibernate.Type.CustomType" kann nicht
in Typ "NHibernate.Type.BooleanType" umgewandelt werden.
The BooleanM1 implementation is like this:
{
public class BooleanM1 : IUserType
{
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(bool); }
}
public SqlType[] SqlTypes
{
get { return new[]{NHibernateUtil.Int16.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if(obj == null ) return false;
return ((string)obj == "-1" || (string)obj == "1") ? true : false;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if(value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
((IDataParameter) cmd.Parameters[index]).Value = (bool)value ? -1 : 0;
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if( ReferenceEquals(x,y) ) return true;
if( x == null || y == null ) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
}
}
Is this an known bug in the linq provider or is there something wrong with my UserType? Any help is appreciated.
I had a similar issue with a UserType that does pretty much the same thing. I found that explicitely stating the equality in my queries resolved the problem.
Try going from:
var pList = Session.Query<Test>().Where( c => c.Active ).ToList();
to:
var pList = Session.Query<Test>().Where( c => c.Active == true ).ToList();
For some reason NHibernate's Linq provider is then able to figure it out.
Have you solved this yet?
Not sure if it is the same issue but I had something similar where the database field was nullable and the custom type specifies that you are returning a "bool" -> It may need to be changed to "bool? for the return type"
public Type ReturnedType
{
get { return typeof(bool?); }
}
Fixed in version 4.1.0.
The user type should implement IEnhancedUserType to work correctly with the fix.
https://nhibernate.jira.com/browse/NH-2839
I've got a problem when getting a record from the database with a boolean column. I can't change the database structure.
The database type is Character(1) (PostgreSQL) where they used 't' for true and 'f' for false. I have used the PostgreSQLDialect.
I've tried to put this in the hibernate-configuration
<property name="query.substitutions">1 't',0 'f'</property>
I've tried to override in the dialect
public override string ToBooleanValueString(bool value)
{
return value ? "t" : "f";
}
The mapping is:
Map(x => x.IsTemplate).Column("template_p");
Still not working,
Any help?
You may have to create your own user type here. Here is an example of creating your own:
http://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/
Your mapping would then become something like this:
Map(x => x.IsTemplate).Column("template_p").CustomType<MyCustomType>();
Edit:
You might also be able to use the standard YesNo type by doing something with your query-substitiutions. I haven't tested this but maybe something like this:
<property name="query.substitutions">yes 't', no 'f'</property>
Your mapping would look pretty much the same as I stated above except you would use the YesNo type.
Indeed Cole W, i made this customtype and it works like a charm
(source link: https://lostechies.com/rayhouston/2008/03/23/mapping-strings-to-booleans-using-nhibernate-s-iusertype/)
public class TFType : IUserType
{
public bool IsMutable
{
get { return false; }
}
public Type ReturnedType
{
get { return typeof(TFType); }
}
public SqlType[] SqlTypes
{
get { return new[]{NHibernateUtil.String.SqlType}; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var obj = NHibernateUtil.String.NullSafeGet(rs, names[0]);
if(obj == null ) return null;
var truefalse = (string) obj;
if( truefalse != "t" && truefalse != "f" )
throw new Exception(string.Format("Expected data to be 't' or 'f' but was '{0}'.", truefalse));
return truefalse == "t";
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
if(value == null)
{
((IDataParameter) cmd.Parameters[index]).Value = DBNull.Value;
}
else
{
var yes = (bool) value;
((IDataParameter)cmd.Parameters[index]).Value = yes ? "t" : "f";
}
}
public object DeepCopy(object value)
{
return value;
}
public object Replace(object original, object target, object owner)
{
return original;
}
public object Assemble(object cached, object owner)
{
return cached;
}
public object Disassemble(object value)
{
return value;
}
public new bool Equals(object x, object y)
{
if( ReferenceEquals(x,y) ) return true;
if( x == null || y == null ) return false;
return x.Equals(y);
}
public int GetHashCode(object x)
{
return x == null ? typeof(bool).GetHashCode() + 473 : x.GetHashCode();
}
}
Haven't tried, but maybe this could work:
Map(x => x.IsTemplate).Formula("case when template_p = 't' then 1 else 0 end")
We are using postgres 8.3-8.4 with boolean values without any problems. However, when using ANTLR3 based HQL parser it started to get picky about boolean substitution, so we made our own dialect with the following override:
public override string ToBooleanValueString( bool value )
{
return value ? "true" : "false";
}
However, we used NHibernate.Dialect.PostgreSQL82Dialect, not the generic one. What postgres version are you running?
EDIT: Oh, sorry, I didn't follow that you hade a character(1) column. maybe this would help instead (with the quotes)?
return value ? "'t'" : "'f'";