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.
Related
I have a situation similar to that described in Fluent NHibernate Mapping not on PK Field
However, the relationship between my tables is described by multiple non-primary key columns.
Imagine Chris Meek's situation but where a Person has a JobType and a Code that, together, should (sorry, it's a legacy database) uniquely describe a Person
Person
------
Id PK
JobType
Code
Name
Order
-----
Id PK
Person_JobType
Person_Code
OrderDetails
Serhat Özgel's answer describes using PropertyRef, but I can't find a way to do that individually for multiple columns. I've tried similar to
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
HasMany(p => p.Order)
.KeyColumns.Add("Person_JobType")
.PropertyRef("JobType")
.KeyColumns.Add("Person_Code")
.PropertyRef("Code")
}
}
But this obviously doesn't work, since KeyColumns.Add() returns another OneToManyPart so PropertyRef() isn't being run against the individual column being added. The second PropertyRef() simply overwrites the first one, and I get the following error:
NHibernate.MappingException : collection foreign key mapping
has wrong number of columns: MyApp.Person.Order type: Int32
I've looked at the various overloads of KeyColumns.Add(),
public TParent Add(string name)
public TParent Add(params string[] names)
public TParent Add(string columnName, Action<ColumnPart> customColumnMapping)
public TParent Add(ColumnMapping column)
Specifically the last two, but couldn't find any way to set PropertyRef individually level for each column :(
Is there a way to do that? Am I going about this the wrong way entirely?
using hbm.xml and FluentNHibernate it is possible with a trick
class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Map(_ => JobTypeAndCode)
.Columns.Add("Person_JobType", "Person_Code")
.ReadOnly()
.LazyLoad() // optional: prevent loading the Columns twice
.Access.None();
HasMany(p => p.Orders)
.KeyColumns.Add("Person_JobType", "Person_Code")
.PropertyRef("JobTypeAndCode")
}
private object JobTypeAndCode { get; set; } // FakeProperty
}
Note: i never got this to work using NHibernate MappingByCode
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.
I'm trying to set up a relationship as follows. Each Master item has one or more Detail items:
public class Detail {
public virtual Guid DetailId { get; set; }
public virtual string Name { get; set; }
}
public class Master {
public virtual Guid MasterId { get; set; }
public virtual string Name { get; set; }
public virtual IList<Detail> Details { get; set; }
}
And Mappings:
public class MasterMap : ClassMap<Master>
{
public MasterMap()
{
Id(x => x.MasterId);
Map(x => x.Name);
HasMany(x => x.Details).Not.KeyNullable.Cascade.All();
}
}
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.Id);
Map(x => x.Name);
}
}
The Master database table is:
masterId uniqueidentifier NOT NULL
name nvarchar(max) NULL
and Detail is:
DetailId uniqueidentifier NOT NULL
name nvarchar(max) NULL
MasterId uniqueidentifier NULL
foreign key (masterId) references [Master]
I don't really care to have a link from Detail back to Master -- in otherwords, Detail objects on their own are just not interesting to my domain layer. They will always be accessed via their Master object.
Using code like this:
Master mast = new Master
{
MasterId = new Guid(),
Name = "test",
Details = new List<Detail>
{
new Detail { .DetailId = new Guid(), .Name = "Test1" },
new Detail { .DetailId = new Guid(), .Name = "Test1" }
}
};
using (transaction == Session.BeginTransaction)
{
Session.Save(mast);
transaction.Commit();
}
This works great, except for a crazy limitation outlined in this post: NHibernate does an INSERT and puts Detail.MasterId as NULL first, then does an UPDATE to set it to the real MasterId.
Really, I don't want Detail entries with NULL MasterIds, so if I set the MasterId field to NOT NULL, the INSERT to Detail will fail, because as I said NHibernate is trying to put in MasterId = NULL.
I guess my question boils down to this:
How can I get the above code sample to work with my existing domain model (eg, without adding a Detail.Master property), and the Detail.MasterId field in the database set to NOT NULL?
Is there a way to get Nhibernate to just put the correct MasterId in the initial INSERT, rather than running an UPDATE afterwards? Is there rationale somewhere for this design decision? -- I'm struggling to see why it would be done this way.
NH3 and above allow to correct save entities in case of uni-directional one-to-many mapping without annoying save null-save-update cycle, if you set both not-null="true" on <key> and inverse="false" on <one-to-many>
FluentNHibernate code snippet for that:
public class MasterMap : ClassMap<Master>
{
public MasterMap()
{
Id(x => x.MasterId);
Map(x => x.Name);
HasMany(x => x.Details)
.Not.Inverse() //these options are very
.Not.KeyNullable() //important and work only if set together
.Not.KeyUpdate() //to prevent double update
.Cascade.All();
}
}
You can't. To quote the link from my answer on the other question you linked to:
Very Important Note: If the <key> column of a <one-to-many> association is declared NOT NULL, NHibernate may cause constraint violations when it creates or updates the association. To prevent this problem, you must use a bidirectional association with the many valued end (the set or bag) marked as inverse="true". See the discussion of bidirectional associations later in this chapter.
Edit: as Hazzik has rightly pointed out, this has changed in NHibernate 3 and above. The docs sadly haven't been updated, so here's Hazzik:
[If you] set inverse="false" and not-null on <key>, NH3 and above will perform only two inserts insead of insert-insert-update.
The reason NHibernate does it this way is because:
When it saves the detail it only knows about the stuff the detail knows about. So any master references which happen in the background are ignored.
Only when the master is saved it sees the relation and updates the elements of the collection with the id of the master.
Which is from an object oriented point of view logical. However from a saving point-of-view is slightly less logical. I suppose you can always file a bug report, or look if it might have been filed already and ask them to change it. But I suppose they have their specific (design/domain) reasons.
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.
I have the following (simplified)
public enum Level
{
Bronze,
Silver,
Gold
}
public class Member
{
public virtual Level MembershipLevel { get; set; }
}
public class MemberMap : ClassMap<Member>
{
Map(x => x.MembershipLevel);
}
This creates a table with a column called MembershipLevel with the value as the Enum string value.
What I want is for the entire Enum to be created as a lookup table, with the Member table referencing this with the integer value as the FK.
Also, I want to do this without altering my model.
To map an enum property as an int column, use method CustomType.
public class MemberMap : ClassMap<Member>
{
Map( x => x.MembershipLevel ).CustomType<int>();
}
In order to keep the enum and lookup table in sync, I would add the lookup table and data to your sql scripts. An integration test can verify that the enum and lookup table values are the same.
If you wanted SchemaExport to create this table, add a class and mapping for it.
public class MembershipLevel
{
public virtual int Id { get; set; }
public virtual string Code { get; set; }
}
public class MembershipLevelMap : ClassMap<MembershipLevel>
{
Id( x => x.Id );
Map( x => x.Code );
}
If you are creating the table with SchemaExport, you will need to populate it as well:
foreach (Level l in Enum.GetValues( typeof( Level ))) {
session.Save( new MembershipLevel{ Id = (int) l, Code = l.ToString() });
}
I wouldn't do that because your Enum declaration is not dynamic, or simpler, it doesn't change without recompiling, while your lookup table may change at any moment. If the Enum's and lookup table's values don't match, what's next?
Another reason is if you change the Enum (in code), you'd have to synchronise it with the database table. Since Enums don't have an incremental key (PK), they can't be synchronised so simple. Let's say you remove one Enum member from your code and recompile it, what is supposed to happen? And if you change a value?
I hope I made my objections to this approach clear. So I strongly recommend storing the name or the value of your enum members. To store it by name, just map like this:
public class MemberMap : ClassMap<Member>
{
Map(x => x.MembershipLevel, "level")
.CustomType<GenericEnumMapper<Level>>()
.Not.Nullable();
}
To store the values, do as #Lachlan posted in his answer.
Or if you really need a lookup table and wants to use an Enum with strict checking, create a normal model with PK (or use value for this), KEY and VALUE. Create your enum with your static members, and make the application query the database for the names and values when you start it. If things don't match, do whatever you need. Additionally, this doesn't guarantee your table won't change while your program is running, so you better be sure it doesn't.