Using string enums in NHibernate - GetValue never gets fired - c#

In my NHibernate driven app I have classes where some properties of the class are not full blown entities. I.e., I have a customer class that has
a customer type property. It seems a waste of resources to create a separate class for the customer type property.
What I'm trying to do is to map the customer type to an enum string in NHibernate 3.2 using mapping by code. Searching on StackOverflow,
I found a possible solution with the following code:
public class Customer
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual CustomerType CustomerType { get; set; }
}
public class CustomerMap : ClassMapping<Customer>
{
public CustomerMap()
{
Id(x => x.Id);
Property(x => x.Name, m => m.Length(100));
Property(x => x.CustomerType, m =>
{
m.Column("CustomerTypeId");
m.Type(typeof (CustomerTypeMap), null);
});
}
}
public enum CustomerType
{
Big_Customer,
Small_Customer
}
public class CustomerTypeMap : EnumStringType<CustomerType>
{
public override object GetValue(object code)
{
return code == null
? string.Empty
: code.ToString().Replace('_', ' ');
}
public override object GetInstance(object code)
{
var str = (string)code;
if (string.IsNullOrEmpty(str)) return StringToObject(str);
else return StringToObject(str.Replace(" ", "_"));
}
}
This solution works only partially. As you see from the code, I'm trying to make the enum string look nice by replacing the underscores in order to get "Big customer" instead of "Big_Customer". This doesn't work. Putting break lines into the code, I noticed that the overrriden GetInstance function gets called as it is supposed to, but the GetValue function never gets fired. Any help would be appreciated.

I agree you shouldn't create a new entity for something like CustomerType. NHibernate doesn't expect you to either. Fluent NHibernate will map enums as strings by default (I assume you're using the latest version), but apparently you want to have more fine-grained control.
It's been a while since I used Fluent NH, but maybe try this (unless the API has been changed):
Map(x => x.CustomerType).Column("CustomerTypeId").CustomType<CustomerTypeMap>();
Once again, not sure if this (still) works as the API changed/changes a lot.

Related

How to use LINQ to query a [key] property using generic types?

Let's say I have a class:
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And now I want to create a generic Get() method that might query Customer or any one of several other classes that also have a [key] field defined.
public T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i. ????? = id);
}
I'm not sure how to use Linq to generically specify the [key] field.
Thanks!
Hope this helps:
public interface IBase
{
int Id { get; }
}
public class Customer : IBase
{
public string Name { get; set; }
public int Id { get ; set ; }
}
public T Get<T>(int id) where T : IBase
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i.Id == id);
}
Just implement the interface IBase in all other classes.
For what is worth I think using contracts is a better way to solve this. But in case you or someone else actually need to check for the attribute here's the answer:
public static T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(
item => (int)item.GetType()
.GetProperties()
.FirstOrDefault(
p => Attribute.IsDefined(p, typeof(KeyAttribute))
).GetValue(item) == id
);
}
As far a this part of your question:
I'm not sure how to use Linq to generically specify the [key] field.
The attribute is KeyAttribute you can know that by navigating to the definition (pressing F12 if you're using VS or checking the docs in case your editor doesn't support this feature.
Things to consider:
this is using Reflection reasonably heavily, so it will never have the best performance. That being said you can cache the result from GetProperties() somewhere for faster lookups.
It's hardcoding the cast to int but it appears that's what you're after.
If the collection is null it'll throw an exception.

Unable to cast object of type 'System.Collections.ArrayList' to type 'System.Collections.Generic.IEnumerable'

I recently upgraded Windows SmartClient solution from nHibernate 2.2 to 4.0 and am getting an exception when writing to the db.
The exception is thrown at this code:
this.session.Save(this.Location); // NHibernate.ISession
tx.Commit(); // exception thrown here
The exception is:
'System.InvalidCastException' in NHibernate.dll
System.InvalidCastException:
Unable to cast object of type 'System.Collections.ArrayList'
to type 'System.Collections.Generic.IEnumerable`1[System.Object]'.
There are several lists in the object being saved, here is a couple representative ones:
protected System.Collections.IList locationList;
public virtual System.Collections.IList AssociatedLocationList
{
get
{
if (this.locationList == null)
{
this.locationList = new System.Collections.ArrayList();
}
return this.locationList;
}
set { this.locationList = value; }
}
protected System.Collections.Generic.IList<Inspection> inspectionList;
public virtual System.Collections.Generic.IList<Inspection> InspectionList
{
get
{
if (this.inspectionList == null)
{
this.inspectionList = new System.Collections.Generic.List<Inspection>();
}
return this.inspectionList;
}
set { this.inspectionList = value; }
}
Note that some have a type specified and some don't.
One suggestion here is set the property to an IList, but I already have it as that.
What can be done?
Support for persistent non-generic collections was removed in NHibernate 4.0. Convert to generic collection instead.
See list of breaking changes in NHibernate 4.0 release notes.
The problem here could be with latest version of NHibernate ..i.e. 4.0
you have following options.
1) Most of the time, new version support backward compatibility. If it is the case, look for overload versions of iSession.Save method. You may get non-generic as well.
2) Possibly new save method only support generic type. Yours is non-generic i.e. ArrayList. If you can change it to Ilist<>, that should help.
3) If you do not have control on Ilis, then you may write converter in between which can convert your Arraylist to Ilist<> and it would work with Save method.
Hope this helps.
I hope I'm understanding your question right and if so, I'm not sure you need to be doing your null check at all. Instead your parent class should have a list of locations that are held in another table entirely.
This is class in which your location list is.
public class Parent
{
public virtual Guid Id { get; set; }
public virtual IList<Location> Locations { get; set; }
//This is the mapping for this class.
public class ParentMapping : ClassMap<Parent>
{
Id(x => x.Id).GeneratedBy.Guid();
//This is what relates your location list to this parent.
//Notice that in the Location object below,
//there's a Owner property which will point back to here.
HasMany(x => x.Locations).Cascade.All();
}
}
And this is the class that defines your locations.
public class Location
{
public virtual Guid Id { get; set; }
public virtual Parent Owner { get; set; }
public virtual string SomeProperty { get; set; }
//This is the mapping for this class.
public class LocationMapping : ClassMap<Location>
{
Id(x => x.Id).GeneratedBy.Guid();
Map(x => x.SomeProperty);
//This will relate our property back to the parent.
References(x => x.Owner);
}
}

Using an extension method on a base class in a LINQ query

Apologies in advance for my naivety.
I am using Entity Framework to persist entities I have defined in my domain model. My domain model entities all inherit from my EntityBase class. This has properties I wish to be common to all my entities:
public class EntityBase
{
public string CreatedBy { get; set; }
public DateTime? Created { get; set; }
public int ModifiedBy { get; set; }
public DateTime? Modified { get; set; }
public bool Enabled { get; set; }
public bool Deleted { get; set; }
}
Now when I want to query EF using LINQ it would be nice if I didn't have to include elements to check if a particular entity is Enabled or Deleted. Every query would involve code, for example:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Select(m => m.Enabled && !m.Deleted)
.ToList();
Rather than doing this each time, I thought I would write an extension method which would act on IQueryable
public static IQueryable<EntityBase> Active(this IQueryable<EntityBase> entityCollection)
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
In my naivety I then thought I could just include this in any LINQ query which returns my entities which inherit from the EntityBase class - like so:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Active() <============================= Extension Methd
.ToList();
return Mapper.Map<List<Message>,List<MessageDto>>(messages);
However, the compiler now complains that:
Error 2 Argument 1: cannot convert from
'System.Collections.Generic.List<Diffusr.Business.Entities.EntityBase>' to
'System.Collections.Generic.List<Diffusr.Business.Entities.Message>'
Question : Can I achieve what I want to achieve, i.e. a common method for all my entities to return only Enabled and not Deleted? If so, how?
Instead of specifying a concrete class, use generics, as most extension methods do:
public static IQueryable<T> Active<T>(this IQueryable<T> entityCollection) where T:EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
I assume you are using some version of .NET earlier than 4.0. Generic covariance wasn't allowed before 4.0 (ie passing an enumerable of a child type when an enumerable of the base type was expected).
Even after 4.0, it's not the absolute best idea to use covariance as the compiler ends up doing a lot of extra checks to do to ensure type safety whenever you try to store some new value to the List. Jon Skeet has a nice article about this
You can by changing the extension method:
public static IQueryable<T> Active(this IQueryable<T> entityCollection)
where T : EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}

How to create a projection of private/protected properties with RavenDb

If I have an object with nothing but private properties such as
public class Foo
{
private int Id { get; set; }
private string Bar { get; set; }
private string Baz { get; set; }
}
and store it in Raven, it will store those properties and everything works like magic. If I want to do some sort of read-only query off of the collection, how would I go about doing so using an index? (I'm actually open to any solution, even if it doesn't use indices.)
Obviously, something like this will not work because of the private access (and dynamic cannot be used in an expression tree):
public class Foo_LineItems : AbstractIndexCreationTask<Foo, FooLineItem>
{
public Foo_LineItems ()
{
Map = foos => foos.Where (x => x.Baz == null)
.Select (x => new { x.Id, x.Bar });
}
}
I'm sure I have overlooked something, but have been searching the web and cannot find anything that answers this specific question. The obvious answer is to segregate the reads and writes, using CQRS, and not actually persist the raw domain object. (This is just an experiment with Raven and CQS.)
We have untyped API for doing this:
public class Foo_LineItems : AbstractIndexCreationTask
{
public override IndexDefinition CreateIndexDefinition()
{
return new IndexDefinition
{
Map = #"
from foo in docs.Foos
where foo.Baz == null
select new { foo.Id, foo.Bar }
"
};
}
}

Fluent nHibernate , IDictionary Confusion

Using the following classes..
public class Trait
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class Sheet
{
public virtual int Id { get; set; }
public virtual IDictionary<Trait, int> Influences { get; set; }
}
I have tried to map them using Fluent nHibernate, as such.
public class TraitMap : ClassMap<Trait>
{
public TraitMap()
{
Id(x => x.Id);
Map(x => x.Name);
Table("Traits");
}
}
public class SheetMap : ClassMap<Sheet>
{
public SheetMap()
{
Id(x => x.Id);
HasManyToMany<Trait>(x => x.Influences)
.Schema("Sheets")
.Table("Influences")
.ParentKeyColumn("Trait")
.ChildKeyColumn("Sheet")
.AsMap<int>("Rating")
.Cascade.All();
Table("Sheets");
}
}
This does not work. I get the exception.
Exception occurred getter of Trait.Id
Now, if I change the Dictionary up to look like this..
public class Sheet
{
public virtual int Id { get; set; }
public virtual IDictionary<int, Trait> Influences { get; set; }
}
Basically making the int the Key, and the Trait the value (not what I want), it does work. Can anyone explain this, and how I can more appropriately reproduce what I am trying to do?
I think the reasoning is because when I specify HasManyToMany<Trait> I am specifying the Value element of the collection. However this is not my intention.
I want to look things up by the Name of the Key, not the Name of the Value. While I realize this is technically an 'acceptable' solution, it kind of goes against the Dictionary convention. I'd prefer to take a more convention over solution approach, if at all possible - and I'd like to better understand what is actually going on under the hood.
What you want is what <composite-index> mapping in hbm would give you. I believe that instead of AsMap, you'd want AsEntityMap. However, look at this thread which talks about a rewrite (in august) of the fluent mapping for Maps that makes all of the above obsolete.
EDIT : For AsEntityMap, and other options, take a look at this SO question and answer
HasMany<Trait>(x => x.Influences)
.KeyColumn("key column name")
.AsEntityMap("referenced column name")
.Entity("dict value", v=> v.Type<int>());
Also, you say you're on the latest version - the latest release is 1.1, but trunk is 2.0, and is very different, and much further along. If you're not on 2.0+, you wouldn't see the methods posted in most of the help threads like the one linked above.

Categories