NHibernate skips certain properties to update, possible? - nhibernate

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.

Related

remove from collection without load all collection data. confused which collection mapping to use

I have a many-to-many relationship between Assignment and User
When trying to delete an user from an assignment, I see all users are loaded in the collection.
How to I avoid that?
public class User
{
public virtual int Id { get; private set; }
public virtual IList<Assignment> Assignments { get; set; }
}
public class Assignment
{
public virtual int Id { get; private set; }
public virtual ICollection<User> Users { get; set; }
}
Mappings:
HasManyToMany(user => user.Assignments).Table("UserToAssignment").ParentKeyColumn("UserId").ChildKeyColumn("AssignmentId").Inverse().ExtraLazyLoad();
HasManyToMany(productAssignment => productAssignment.Users).AsSet().Table("UserToAssignment").ParentKeyColumn("AssignmentId").ChildKeyColumn("UserId").LazyLoad();
Calling code:
assignment.Users.Remove(user)
Initially I used Bag instead of Set for Assignment mapping, but when updating it, it was deleting and then reinserting alot of rows in the AssignmentsToUsers table. So I changed to using Set.
But now I see a problem with using Set: it brings all data in memory.
What is the recommended way of doing this?
You can't avoid this and I would ignore it if performance is acceptable. If performance is a problem, there are three ways I can think of to tackle it:
If the other side of the collection (User.Assignments) is lighter weight then remove the assignment from the user instead.
Model the many-to-many table and delete the object directly. You would have to be certain that the Users collection is not going to be loaded prior to this because the in-memory representation will still contain the deleted record.
Direct delete using SQL -- this has the same caveat as #2.
You should use extra lazy mode also for Assignment.Users.

NHibernate configuration for uni-directional one-to-many relation

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.

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.