Fluent NHibernate: IEnumerable component mapping from a single query? - nhibernate

Hoping you can help me with a mapping problem I have. At the moment I have a view which returns something such as:
Name | Title | Price | Date | Format | FormatPriority |
Example data may be:
Bob | Credits | 340 | 01/01/2010 | BAR | 10 |
Bob | Credits | 340 | 01/01/2010 | FOO | 20 |
Bob | Credits | 340 | 01/01/2010 | WOO | 40 |
What I want is a domain model which looks like this:
string Name;
string Title;
int Price;
DateTime Date;
IEnumerable Formats;
Format class would then have:
string Type
int Priority
At the moment we are using the ClassMap approach within Fluent NHibernate (not auto config). How would we map this? The Component doesn't seem to support a collection and this isn't a HasMany relationship as it's coming back as part of the same query.
Any ideas??
Thanks
Ben

Disclaimer: This is such a huge hack, it pains me to post it.
This is based on the schema you've provided, so it might need to be modified to accomodate a different design. There probably is a much better way to do this, but hopefully this should get you going again at least.
The issue is you have a bit of a mismatch in your model and query. Your query returns multiple rows which you intend to be for a single entity with multiple components, but NHibernate is geared to interpret that as multiple entities each with a single component.
NHibernate supports collections of components, but only when they're stored in a separate table/view. These components are joined via a foreign key back to the entity table. If you can change your design to support this, please do so!
If not, the only option I could think of is a self-join on your view. It won't produce the most optimised query, but it should do the trick.
You didn't mention what your entity was called, so I've gone with Transaction.
public class Transaction
{
public virtual string Name { get; set; }
public virtual string Title { get; set; }
public virtual decimal Price { get; set; }
public virtual DateTime Date { get; set; }
public virtual ISet<Format> Formats { get; set; }
}
public class Format
{
public virtual string Type { get; set; }
public virtual int Priority { get; set; }
// OVERRIDE EQUALITY MEMBERS!
}
The mapping I've used is:
public class TransactionMap : ClassMap<Transaction>
{
public TransactionMap()
{
Table("vwTransactions");
Id(x => x.Name);
Map(x => x.Title);
Map(x => x.Price);
Map(x => x.Date);
HasMany(x => x.Formats)
.Table("vwTransactions")
.KeyColumn("Name")
.Component(c =>
{
c.Map(x => x.Type, "Format");
c.Map(x => x.Priority, "FormatPriority");
})
.Fetch.Join();
}
}
So you can see the mapping is pointed at the vwTransactions view. You didn't specify an id in your schema, so I've used Name as a identity (this is important). Skip down to the HasMany now, you can see that also points at vwTransactions; NHibernate will see this and do a self-join on the view. Then the key column is set to Name, the same as the entity Id; this way NHibernate will use that to resolve the references between the component and the entity, rather than trying to use an integer foreign key. The Fetch.Join will force NH to eagerly fetch this relationship, so at least we save a bit there. Last thing of note, the Formats property is an ISet, if you don't do this you'll end up with duplicate components.
If you now create a criteria (or hql) query for Transaction, you'll get back your entities with their components; however, you'll get duplicates due to the multiple rows being brought back per entity. This is fairly common, and easily resolved using the DistinctRootEntity transformer.
var transactions = session.CreateCriteria(typeof(Transaction))
.SetResultTransformer(Transformers.DistinctRootEntity)
.List<Transaction>();
That should be it, you'll now end up with just one entity (based on your dataset) with 3 components.
Nasty, I know.

Related

FluentNHibernate - Mapping a class to multiple tables

Sorry for a lengthy question. But it is worth giving all the details so please bear with me through to the end.
I'm working against a legacy database over which I do not have much control. I want to be able to map a class to multiple database tables. Here is how my tables look
Lookup
+--------+--------------+------------+
| Column | DataType | Attributes |
+--------+--------------+------------+
| Id | INT | PK |
| Code | NVARCHAR(50) | |
+--------+--------------+------------+
Culture
+--------------+--------------+------------+
| Column | DataType | Attributes |
+--------------+--------------+------------+
| Id | INT | PK |
| Culture_Code | NVARCHAR(10) | |
+--------------+--------------+------------+
Lookup_t9n
+----------------+---------------+---------------------+
| Column | DataType | Attributes |
+----------------+---------------+---------------------+
| Id | INT | PK |
| Culture_Id | INT | FK to Culture table |
| Localised_Text | NVARCHAR(MAX) | |
+----------------+---------------+---------------------+
As you can see, I have a lookup table where all lookups are stored. The display text for a lookup is localized and stored in a separate table. This table has a foreign key to culture table to indicate the culture for which the localized text exists.
My class looks like this
public class Lookup {
public virtual int Id {get; set;}
public virtual string Code {get; set;}
public virtual string DisplayText {get; set;}
}
And my FNH mapping class looks like this
public class LookupMappings : ClassMap<Lookup> {
public LookupMappings()
{
Table("Lookup");
Id(x => x.Id).Column("Id");
Map(x => x.Code).Column("Code");
Join("Lookup_t9n", join => {
join.Map(x => x.DisplayText).Column("Localised_Text"); //Note this place, my problem is here
})
}
}
In the above mapping, in Join part I want to provide some where clause like WHERE Lookup_t9n.Culture_Id = Culture.Culture_Id AND Culture.Culture_Code = System.Threading.Thread.CurrentUICulture.CultureCode.
I know this is not a valid SQL but conveys the intent I hope. Has anyone have any experience of doing such a thing.
I can add a mapping layer where I can have classes that map one-to-one with database tables and then write plain c# to map those classes back to my Lookup class. I have rather done that as an interim solution. I was wondering if I can remove that mapping layer with some smart NH use.
I do not have simple answer, like CallThis(). I would like to give you suggestion, based on how we are using similar stuff. The solution is base on the standard mapping, hidding its complexity in C# Entities. It is just a draft of the solution so I'll skip the middle Culture table, and will expect that in Lookup_t9n we do store just a culture name (en, cs...)
Let's have this class
public class Lookup {
public virtual int Id {get; set;}
public virtual string Code {get; set;}
// for simplicity skipping null checks
public virtual DisplayText { get { return Localizations.First().LocalizedText; } }
public virtual IList<Localization> Localizations {get; set;}
}
public class Localization { // mapped to Lookup_t9n
public virtual string CultureName {get; set;}
public virtual string LocalizedText {get; set;}
}
Having this, we can map the collection of Localizations as HasMany. It could even be mapped as a component (see example of component mapping)
Now, what we do need is to introduce a filter. Example with Fluent. The essential documentation: 18.1. NHibernate filters.
Simplified mapping
filter:
public class CulturFilter : FilterDefinition
{
public CulturFilter()
{
WithName("CulturFilter")
.AddParameter("culture",NHibernate.NHibernateUtil.String);
}
collection:
HasMany(x => x.Localization)
.KeyColumn("Id")
...
.ApplyFilter<CulturFilter>("CultureName = :culture"))
.Cascade.AllDeleteOrphan();
Finally, we have to introduce some AOP filter, IInterceptor... which will be triggered each time (needed) and adjust the ISession
session
.EnableFilter("CulturFilter")
.SetParameter("culture"
,System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName);
And now we have Localized string based on current culture, while using standard mapping of localized values as a collection.

NHibernate skips certain properties to update, possible?

I defined my entities with bunch of columns and created mapping.
public class PurchaseRecord {
public virtual int? Id {
get;
set;
}
public virtual DateTime? PurchasedDate {
get;
set;
}
public virtual string Comment {
get;
set;
}
public virtual IList<PurchaseRecordExtendedProperty> ExtendedPropertyValues {
get;
set;
}
public class PurchaseRecordMap : ClassMap<PurchaseRecord> {
public PurchaseRecordMap() {
Table("PurchaseRecords");
Id(x => x.Id, "RecordID").GeneratedBy.Identity();
Map(x => x.PurchasedDate, "PurchaseDate").Not.Nullable();
Map(x => x.Comment, "Comment");
HasMany(x => x.ExtendedPropertyValues).KeyColumn("ExtendedPropertyID").Cascade.All();
}
It works well in most of the cases, howerver in some certain situation I want to skip updating certain column (such as child collection ExtendedPropertyValues). When I create the PurchaseRecord object I don't even bother to load the data of ExtendedPropertyValues. But if the property is null NHibernate tries to delete the child records from database.
I know there are some scenario that the ExtendedPropertyValues will never be changed. For performance consideration I don't want to load the data I don't need, is there a way I can force NH to skip designated properties if I don't need to update?
Thanks for any suggestion.
If you enable lazy loading, NHibernate will not try to load any child collections, they will be initialized to a proxy which will only load them if you access them. If you set the child collection to null, that is effectively telling NHibernate to delete all entries in that relationship (unless you mark the relationship as inverse).
NHibernate will not try to update the child collections unless they change (which setting it to null would do).
In summary, enable lazy-loading, and mark ExtendedPropertyValues as inverse, and it should not update it unless you change ExtendedPropertyValues, it also will not load ExtendedPropertyValues unless you access it.

Writing computed properties with NHibernate

I'm using NHibernate 2.1.2 + Fluent NHibernate
I have a ContactInfo class and table. The Name column is encrypted in the database (SQL Server) using EncryptByPassphrase/DecryptByPassphrase.
The following are the relevant schema/class/mapping bits:
table ContactInfo(
int Id,
varbinary(108) Name)
public class ContactInfo
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class ContactInfoMap : ClassMap<ContactInfo>
{
public ContactInfoMap()
{
Id(x => x.Id);
Map(x => x.Name)
.Formula("Convert(nvarchar, DecryptByPassPhrase('passphrase', Name))");
}
}
Using the Formula approach as above, the values get read correctly from the database, but NHibernate doesn't try to insert/update the values when saving to the database (which makes sense).
The problem is that I would like to be able to write the Name value using the corresponding EncryptByPassPhrase function. I'm unsure if NHibernate supports this, and if it does, I haven't been able to find the correct words to search the documentation effectively for it.
So... how can I write this computed property back to the database with NHibernate?
Thanks in advance!
A property mapped to a formula is read-only.
A named query wrapped up in a ContactInfoNameUpdater service might be one way to solve the problem.

NHibernate: Map same class to multiple tables depending on parent

I have a model where multiple classes have a list of value types:
class Foo { public List<ValType> Vals; }
class Bar { public List<ValType> Vals; }
Foo and Bar are unrelated apart from that they both contain these vals. The rules for adding, removing, etc. the ValTypes are different for each class. I'd like to keep this design in my code.
There are times when I want to copy some Vals from a Foo to a Bar, for example. In the database, each ValType has its own table, to keep it small, light (it just has the parent ID + 2 fields), and allow integrity checks. I know NHibernate says I should keep my objects as granular as the database, but that just makes my code uglier.
The best I've thought of so far is to make separate subclasses of ValType, one for each parent. Then I can map those at that level. Then, I'll hook up add and remove logic to auto-convert between the right subclasses, and actually store them in a private list that has the right subclass type. But this seemed a bit convoluted.
How can I map this in NHibernate (Fluent NHibernate if possible)?
Please let me know if this is a duplicate -- I'm not quite sure how to search this.
At database level a solution would be to have:
Val(Id)
Bar(Id)
BarToVal(IdBar, IdVal)
FooToVal(IdFoo, IdVal)
I am not very sure how would these be mapped. Maybe something like:
// BarMap:
HasManyToMany(x => x.Vals).WithTableName("BarToVal");
// FooMap:
HasManyToMany(x => x.Vals).WithTableName("FooToVal");
Hope it's making sense...
You can find an example on the Google Code page for Fluent NHibernate.
Model
public class Customer
{
public string Name { get; set; }
public string Address { get; set; }
}
Schema
table Customer (
Id int primary key
Name varchar(100)
)
table CustomerAddress (
CustomerID int,
Address varchar(100)
)
Mapping
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id);
Map(x => x.Name);
WithTable("CustomerAddress", m =>
{
m.Map(x => x.Address);
});
}
}
In this example, an entity is split across two tables in the database. These tables are joined by a one-to-one on their keys. Using the WithTable feature, you can tell NHibernate to treat these two tables as one entity.

Mapping a dictionary in Fluent Nhibernate through a secondary key

I have a legacy DB which uses a guid to map children to the parent entity.
In my domain layer, I'd prefer to obscure this quirk, so I'd like to have my parent entity look like this:
public class Parent
{
virtual public int Id {get; protected set; }
virtual public string ParentContent { get; set; }
virtual public Guid ReferenceId { get; set; }
virtual public IDictionary<int,Child> Children { get; set; }
}
public class Child
{
virtual public int Id { get; protected set; }
virtual public Parent { get; set; }
virtual public string ChildContent { get; set; }
}
The parent would then map each Child.Id to Child in the Children dictionary. The Child mapping works fine, but I can't seem to find a reasonable mapping for the parent.
A field named ParentReferenceID exists in both Parent and Child tables, so I've attempted to map this with something like this:
mapping.HasMany<Broker>(x => x.Children)
.Table("Child")
.KeyColumn("ParentReferenceID")
.Inverse()
.AsMap<long>(index=>index.Id,val=>val.Type<Broker>());
Unfortunately, this produces an error:
The type or method has 2 generic parameter(s), but 1 generic argument(s) were provided. A generic argument must be provided for each generic parameter.
To simplify my problem, I started by trying Bag semantics, replacing the Parent's IDictionary with an IList. This was mapped using something like:
mapping.HasMany<Broker>(x => x.Brokers)
.Table("Child")
.KeyColumn("ParentReferenceId")
.Inverse()
.AsBag();
That produces the more obvious exception,
System.Data.SqlClient.SqlException: Operand type clash: uniqueidentifier is incompatible with int
Unfortunately, I can't seem to figure out the right way to tell it to join on the ReferenceID field. What's the right way to do that? I'd prefer the dictionary, but I'd be reasonably happy if I could even get the bag to work.
For clarity, I'm using a build of Fluent that is bundled with a recent SharpArchitecture pulled from git. The Fluent dll is marked version 1.0.0.594, but if a more recent build would help, I'm flexible.
Further digging has led me to a solution for the Bag case, though the dictionary is still giving me a bit of trouble.
The solution requires a patch to Fluent NHibernate's OneToManyPart mapping class. (Hat tip to This bug report: Could not map a one-to-many relationship where the key is not the primary key.
mapping.HasMany(x => x.Children)
.Table("Child").KeyColumn("ParentReferenceId")
.PropertyRef("ReferenceId")
.Inverse()
.AsBag();
Theoretically, AsMap should work almost the same way, but for some reason that I'm not entirely clear on, it doesn't work for me. I'll explore that later, but I'm open to suggestions.