How do I load an entity from a database where the Composite ID of the record contains a key-property with a null value? - oracle

I am reading data from an external organisation using Oracle.DataAccess through the Oracle10g provider. One of the tables has a composite id made up of these fields.
course:
institutioncode: "X11"
coursecode: "N100"
campuscode: "A"
entryyear: 2011
entrymonth: 10
The problem is that the campus code is allowed to be null by the external provider instead of empty. This leads to nHibernate returning collections that contain null references instead of course entities.
Other domain objects will use these fields to refer to this course entity as well, so this is actually used as a key and I can't easily remap to use a surrogate key.
From the source in tag 3.1.0GA, the check that is causing this behavior can be found in Nhibernate.Type.ComponentType.Hydrate(IDataReader rs, string[] names, ISessionImplementor session, object owner). This is always refusing the possibility that a key-property could be null. Could this change to make nullability an option on key-property and key-reference properties?
Failing that, how would you recommend reading this data directly with nHibernate?

NULL values in properties are not supported by design.
There are two ways to deal with this:
Import the data instead of using it raw from the source, adding a proper surrogate key.
Handle that entity without NHibernate.

ok my first comment didnt worked out on References (ManyToOne). So here my alternative solution: a usertype to work around the check.
class CourseMap : ClassMap<Course>
{
public CourseMap()
{
CompositeId()
.KeyProperty(c => c.InstitutionCode)
.KeyProperty(c => c.CourseCode)
.KeyProperty(c => c.CampusCode, key => key.Type(typeof(MyUserType)))
.KeyProperty(c => c.EntryYear)
.KeyProperty(c => c.EntryMonth);
}
}
class MyUserType : IUserType
{
public object Assemble(object cached, object owner)
{
return DeepCopy(cached);
}
public object DeepCopy(object value)
{
return value;
}
public object Disassemble(object value)
{
return DeepCopy(value);
}
public new bool Equals(object x, object y)
{
return object.Equals(x, y);
}
public int GetHashCode(object x)
{
return (x == null) ? 0 : x.GetHashCode();
}
public bool IsMutable
{
get { return false; }
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var value = NHibernateUtil.String.NullSafeGet(rs, names[0]);
return (value == null) ? string.Empty : value;
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
string d = string.IsNullOrEmpty((string)value) ? null : (string)value;
NHibernateUtil.String.NullSafeSet(cmd, d, index);
}
public object Replace(object original, object target, object owner)
{
return DeepCopy(original);
}
public Type ReturnedType
{
get { return typeof(string); }
}
public SqlType[] SqlTypes
{
get { return new[] { SqlTypeFactory.GetString(100) }; }
}
}
class SomeEntityMap : ClassMap<SomeEntity>
{
public EntityMap()
{
Id(e => e.Id).GeneratedBy.Assigned();
References(e => e.Course)
.Columns("InstitutionCode", "CourseCode", "CampusCode", "EntryYear", "EntryMonth")
.Fetch.Join(); // important because we can't rely on values, NULL is invalid value
}
}

Related

How to compare two lists of objects in C#?

Lets say I have a list of objects:
public class MyObject
{
public int ID {get;set;}
public string Name {get;set;}
public DateTime StartDate {get;set;}
}
now the data:
{0, "my", DateTime.Now}
{0, "List", DateTime.Now}
{0, "Data", DateTime.Now}
Now say I stripped this data from an XML or Excel document and I want to compare with what is in the database and Ignore data that already exists. I am using Entity Framework to handle this.
public IEnumerable<MyObject> GetBy()
{
return _context.MyObjects.OrderBy(x => x.Name);
}
I know if you want to get data from a list where it is different you use:
var myNewStuff = myStuff.Except(myDataBaseStuff);
But none of the ID properties will match so that won't work
How do I compare the two lists BASED on the Name AND StartDate values?
You need to implement your own IEqualityComparer and use the overload of Except that takes it. Using Resharper, I generated this one:
public sealed class NameStartDateEqualityComparer : IEqualityComparer<MyObject>
{
public bool Equals(MyObject x, MyObject y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return string.Equals(x.Name, y.Name) && x.StartDate.Equals(y.StartDate);
}
public int GetHashCode(MyObject obj)
{
unchecked
{
return ((obj.Name != null ? obj.Name.GetHashCode() : 0)*397) ^ obj.StartDate.GetHashCode();
}
}
}
Notice that the comparer only examines the Name and StartDate properties. You can of course change the comparer to your liking, like truncating the milliseconds off of the StartDate before comparing them. Then use it as such:
var myNewStuff = myStuff.Except(myDataBaseStuff, new NameStartDateEqualityComparer());

Trying to save comma-separated list

Trying to save selections from a CheckBoxList as a comma-separated list (string) in DB (one or more choices selected). I am using a proxy in order to save as a string because otherwise I'd have to create separate tables in the DB for a relation - the work is not worth it for this simple scenario and I was hoping that I could just convert it to a string and avoid that.
The CheckBoxList uses an enum for it's choices:
public enum Selection
{
Selection1,
Selection2,
Selection3
}
Not to be convoluted, but I use [Display(Name="Choice 1")] and an extension class to display something friendly on the UI. Not sure if I can save that string instead of just the enum, although I think if I save as enum it's not a big deal for me to "display" the friendly string on UI on some confirmation page.
This is the "Record" class that saves a string in the DB:
public virtual string MyCheckBox { get; set; }
This is the "Proxy", which is some sample I found but not directly dealing with enum, and which uses IEnumerable<string> (or should it be IEnumerable<Selection>?):
public IEnumerable<string> MyCheckBox
{
get
{
if (String.IsNullOrWhiteSpace(Record.MyCheckBox)) return new string[] { };
return Record
.MyCheckBox
.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(r => r.Trim())
.Where(r => !String.IsNullOrEmpty(r));
}
set
{
Record.MyCheckBox = value == null ? null : String.Join(",", value);
}
}
To save in the DB, I am trying to do this in a create class:
proxy.MyCheckBox = record.MyCheckBox; //getting error here
but am getting the error:
Cannot implicitly convert 'string' to System.Collections.Generic.IEnumerable'
I don't know, if it's possible or better, to use Parse or ToString from the API for enum values.
I know that doing something like this will store whatever I put in the ("") into the DB, so it's just a matter of figuring out how to overcome the error (or, if there is an alternative):
proxy.MyCheckBox = new[] {"foo", "bar"};
I am not good with this stuff and have just been digging and digging to come up with a solution. Any help is much appreciated.
You can accomplish this using a custom user type. The example below uses an ISet<string> on the class and stores the values as a delimited string.
[Serializable]
public class CommaDelimitedSet : IUserType
{
const string delimiter = ",";
#region IUserType Members
public new bool Equals(object x, object y)
{
if (ReferenceEquals(x, y))
{
return true;
}
var xSet = x as ISet<string>;
var ySet = y as ISet<string>;
if (xSet == null || ySet == null)
{
return false;
}
// compare set contents
return xSet.Except(ySet).Count() == 0 && ySet.Except(xSet).Count() == 0;
}
public int GetHashCode(object x)
{
return x.GetHashCode();
}
public object NullSafeGet(IDataReader rs, string[] names, object owner)
{
var outValue = NHibernateUtil.String.NullSafeGet(rs, names[0]) as string;
if (string.IsNullOrEmpty(outValue))
{
return new HashSet<string>();
}
else
{
var splitArray = outValue.Split(new[] {Delimiter}, StringSplitOptions.RemoveEmptyEntries);
return new HashSet<string>(splitArray);
}
}
public void NullSafeSet(IDbCommand cmd, object value, int index)
{
var inValue = value as ISet<string>;
object setValue = inValue == null ? null : string.Join(Delimiter, inValue);
NHibernateUtil.String.NullSafeSet(cmd, setValue, index);
}
public object DeepCopy(object value)
{
// return new ISet so that Equals can work
// see http://www.mail-archive.com/nhusers#googlegroups.com/msg11054.html
var set = value as ISet<string>;
if (set == null)
{
return null;
}
return new HashSet<string>(set);
}
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 { return new[] {new SqlType(DbType.String)}; }
}
public Type ReturnedType
{
get { return typeof(ISet<string>); }
}
public bool IsMutable
{
get { return false; }
}
#endregion
}
Usage in mapping file:
Map(x => x.CheckboxValues.CustomType<CommaDelimitedSet>();

Partition Key Question

Let's I define my entity like this:
public class User : TableServiceEntity
{
/// <summary>Unique Id (Row Key)</summary>
public string Id
{
get { return RowKey; }
set { RowKey = value; }
}
/// <summary>Gets or sets the username (Partition Key)</summary>
public string Username
{
get { return PartitionKey; }
set { PartitionKey = value; }
}
}
will the following Linq query use the partition key or not?
var ctx = ServiceLocator.Get<UserDataServiceContext>();
return ctx.Users.Where(x => x.Username == Username).FirstOrDefault();
Pretty sure it will not. You can verify this with Fiddler however. If you do not see a $filter on the wire with PartitionKey and RowKey in it, then you are not querying by them (which is probably bad). I believe what you will have on the wire is an entity that has 4 properties (PK, RK, Id, and Username) and the properties that you will be querying on will not be indexed.

Creating linq expression with a subtype restriction

I have this list of type IEnumerable<MyBaseType> for which I am trying to create an extra where-clause to retrieve a specific item in the list. The specific value does only exist on subtype MyFirstType and MySecondType. Not on MyBaseType.
Is it possible to create an expression kind of...
MyList.Where(b => (b is MyFirstType || (b is MySecondType)) && b.SpecificValue == message.SpecificValue);
Above is not working since b is of type MyBaseType and SpecificValue does not exist there. Also note that I do have another subtype MyThirdType that neither has the SpecificValue.
What does work doing what I want is this...
foreach (dynamic u in MyList)
{
if (u is MyFirstType || u is MySecondType)
{
if (u.SpecificValue == message.SpecificValue)
{
//Extracted code goes here
break;
}
}
}
Anyone have an idea how to create an linq expression for the above scenario?
Maybe there is a better solution but as I see it, this could work well enough... If you don't mind performance.
Well then, start by declaring an interface:
public interface IMySpecialType
{
object SpecificValue {get; set;} //you didn't specify what type this is
//all your other relevant properties which first and second types have in common
}
Then, make MyFirstType and MySecondType derive from this interface:
public class MyFirstType : MyBaseType, IMySpecialType
{
//snipet
}
public class MyFirstType : MySecondType, IMySpecialType
{
//snipet
}
Then, filter and cast:
MyList
.Where(b => (b is MyFirstType) || (b is MySecondType))
.Cast<IMySpecialType>()
.Where(b => b.SpecificValue == message.SpecificValue);
//do something
The direct translation of your code to a Linq where clause is
string messageValue = "foo";
var result = baseList.Where(item =>
{
dynamic c = item;
if(item is MyFirstType || item is MySecondType)
{
if( c.SpecificValue == messageValue)
return true;
}
return false;
});
This will require testing the type of the class though and using dynamic - so you might as well cast item to either MyFirstType or MySecondType directly.
An alternative would be using reflection to check if the property exists, using this approach you are not dependent on the actual types of your items as long as they do have the property you are interested in:
string messageValue = "foo";
var result = baseList.Where( item =>
{
var prop = item.GetType().GetProperty("SpecificValue");
if (prop != null && prop.GetValue(item, null) == messageValue)
return true;
else return false;
});
If modifying the class hierarchy is an option you can have you MyFirstType or MySecondType implement an interface that holds the property, then you can use OfType() in your Linq query:
interface ISpecific
{
string SpecificValue { get; set; }
}
class MyFirstType : MyBase, ISpecific
{
public string SpecificValue { get; set; }
}
...
string messageValue = "foo";
var result = baseList.OfType<ISpecific>()
.Where(item => item.SpecificValue == messageValue);
A far more easy way to do that would be to create an interface to mark all your classes having this property SpecificValue. Then it's a child play :
static void Main(string[] args)
{
List<MyBaseType> MyList = new List<MyBaseType>();
ISpecificValue message = new MyFirstType();
MyList.OfType<ISpecificValue>().Where(b => b.SpecificValue == message.SpecificValue);
}
}
class MyBaseType { }
interface ISpecificValue { string SpecificValue { get; set; } }
class MyFirstType : MyBaseType, ISpecificValue
{
public string SpecificValue;
}
class MySecondType : MyBaseType, ISpecificValue
{
public string SpecificValue;
}

change the constraint name in fluentNhibernate auto mapping alteration

i'm using oracle with FluentNHibernate automapping with alterations & NHibernate
the problem is how to specify the constraint name by overriding the mapping model??
the generated sql like this:
alter table FirstTable
add constraint FK_VerLongIdentifierLongerThan30Characther
foreign key (FirstTableID)
references SecondTable;
i need to change the "FK_VerLongIdentifierLongerThan30Characther" to smaller identifier by overriding the mapping model like this:
model.Override<SomeClass>(m =>
{
m.HasOne<SomeOtherClass>(c => c.SomeProperty).?????????;
//or
m.????????
}
)
Instead of doing an override for each class with this problem, what I have done is create a mapping convention which would truncate objects with surpass a 30-character naming scheme on foreign keys, has many and many to many relations:
public class OracleIHasManyConvention : IHasManyConvention
{
public void Apply(IOneToManyCollectionInstance instance)
{
var keyName = string.Format(CultureInfo.InvariantCulture, "FK_{0}_{1}",
instance.Member.Name,
instance.EntityType.Name).Truncate(30);
instance.Key.ForeignKey(keyName);
}
}
public class OracleForeignKeyConvention : FluentNHibernate.Conventions.ForeignKeyConvention
{
protected override string GetKeyName(Member property, System.Type type)
{
var name = property == null
? "Id_" + type.Name.ToUnderscoredNaming()
: "Id_" + property.Name.ToUnderscoredNaming();
return name.Truncate(30);
}
}
Then I would call these conventions like this:
var cfg = Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile("foobar.lite3"))
.Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<Product>()
.Where(a => a.Namespace == typeof (Product).Namespace)
.Conventions.Add<OracleIHasManyConvention>()
.Conventions.Add<OracleForeignKeyConvention>()
.Conventions.Add<OracleGeneralConvention>()
.Conventions.Add<CascadeAllConvention>()
));
Here's the Truncate extension:
public static class StringHelper
{
public static string Truncate(this string text, int endIndex)
{
if (text.Length > endIndex)
{
text = text.Substring(0, endIndex).TrimEnd('_');
}
return text;
}
}
I hope this is useful in any way. :)

Resources