I have a collection which is filtered at the mapping level to enable soft deletion using an "isDeleted" column in the database.
The mapping looks like this:
HasMany(x => x.UploadedFiles).Where("IsDeleted = 0")
When I set the isDeleted property for some items the collection does not automatically refresh to reflect the deletion until I reload the entity.
Is there any way to force a "refiltering" without reloading the entity ?
The Where clause in the mapping is to filter during fetching. It is not used at run-time, which is why you're not seeing UploadedFiles drop out of your collection when you set IsDeleted = true. I don't believe it is possible to refresh the collection without reloading the entity that owns it.
I would recommend expressing your intent in your object model.
private IList<File> uploadedFiles = new List<File();
public virtual IEnumerable<File> UploadedFiles {
get {
return uploadedFiles.Where(x => x.IsDeleted == false);
}
}
And then modifying your mapping to access your backing field...
HasMany(x => x.UploadedFiles)
.Access.CamelCaseField()
.Where("IsDeleted = 0")
Related
I'm using EF6 with graphdiff and EDMX and must ignore a property of a particular entity.
How should I do since even getting the property the insert or update always leave the NULL field?
The way I was able to work around this while still benefiting from the ease of GraphDiff was as follows:
Set your object equal to the GraphDiff method
Set each property you wish to ignore to .IsModified = false
(Example)
user = db.UpdateGraph(user, map => map
.AssociatedCollection(u => u.UserRoles)
.AssociatedCollection(u => u.Teams));
db.Entry(user).Property(u => u.Password).IsModified = false;
db.Entry(user).Property(u => u.Salt).IsModified = false;
_context.SaveChanges();
I'm using by code mappings and trying to map a manytomany. This works fine but I need OrderBy for the child collection items. I noticed this has been omitted (it does exist in the HBM mappings). e.g.
public class Mapping : EntityMapper<Category>
{
public Mapping()
{
Set(x => x.Items, m =>
{
m.Table("ItemCategories");
m.Key(k => k.Column("CategoryId"));
m.Inverse(true);
m.Cascade(Cascade.None);
}, col => col.ManyToMany(m =>
{
m.Columns(x => x.Name("ItemId"));
//m.OrderBy("Score desc"); // missing in Nh4.x?
}));
}
}
Is there a workaround for this? I tried following the suggestion in this article whereby I can set the property before the session factory is built but it has no effect. e.g.
cfg.GetCollectionMapping(typeof(Category).FullName + ".Items").ManyToManyOrdering = "Score desc";
cfg.BuildSessionFactory();
Am I doing something wrong or is OrderBy on manytomany not supported in Nh4?
Also, is it possible to restrict the maximum number of items retrieved in the collection?
Replaced the many to many with one to many and introduced an entity that represents the relationship (followed advice from this article).
This has the upside of allowing you to map the order-by column as well as other columns, and also solved the issue of restricting the number of items in the collection by using the one-to-many's Where() and Filter() clauses.
I have parent and child objects BOOKLET and DEMOGRAPHICS_INFO in my Oracle database mapped as follows in my data layer:
public BookletMapping()
{
Table("BOOKLET");
Id(x => x.Id, m => m.Column("ID");
...
ManyToOne(x => x.DemographicsInfo, m => m.Column("DEMOGRAPHICS_INFO_ID"));
}
public DemographicsInfoMapping()
{
Table("DEMOGRAPHICS_INFO");
Id(x => x.Id, m => m.Column("ID");
...
}
I have intentionally left out the DemographicsInfo relationship to the Booklet because I don't need to traverse my entities that direction. The ManyToOne relationship will actually be a one-to-one.
I have written a test to ensure I can create a DemographicsInfo and immediately assign it to its parent Booklet, and that looks like this:
[Test]
public void ShouldSaveCorrectEntity()
{
var booklet = _unitOfWork.Get<Booklet>(4);
var demInfo = new DemographicsInfo();
_unitOfWork.Insert(demInfo);
booklet.DemographicsInfo = demInfo;
_unitOfWork.Save();
demInfo.Id.ShouldNotEqual(0);
}
When I call Save(), I get the following exception:
{"ORA-02291: integrity constraint (<schema>.BOOKLET_DEMOGRAPHICS_INFO_FK1) violated - parent key not found\n"}
This is because the demInfo object is not given an Id upon Insert(). My Insert implementation looks like this:
public void Insert<T>(T entity)
{
using (var transaction = _session.BeginTransaction())
{
_session.Save(objectToSave);
_session.Flush();
transaction.Commit();
}
}
Where _session is an NHibernate ISession. Because I have both Saved the new entity (it persists successfully) and Flushed my session, I would expect my demInfo variable to have an Id, but it remains 0, which is a foreign key violation when I try to save my parent object. Am I overlooking a step here? Should I rethink my pattern for adding a new child to an existing parent?
I have solved my issue. As it turns out, the Oracle convention of using Sequences for Id column values and populating them using Triggers on row insert disallows NHibernate from learning the Id upon persisting an entity. I added a Sequence Generator to my Id maps and Insert() now updates the Id on my transient entity upon save so I can assign the DemographicsInfo to my Booklet.DemographicsInfo.
Id(x => x.Id, m =>
{
m.Column("ID");
m.Generator(Generators.Sequence, a => a.Params(new { sequence = "SEQ_TABLE_ID" }));
});
Now my test passes with no exceptions.
I try to map NHibernate to an existing database structure. Unfortunately I can not change the existing structure.
First I'll give some background and then explain the problem itself
Relational analysis is quite simple:
Log is the main Entity. he has one-to-one relationship with Form.
the foreign key is FormID.
Buyer & Seller are collection of the form entity.
The application should save the form entity together with the buyers and the sellers.
The problem is the primary key consisting of the Buyer & Seller entity.
The key composite from ForegineKey- FormID and an intenal ordering int number- tdNum
This is reflected in the following form FNH mapping
public class FormLogMap : ClassMap<FormLog>
{
public FormLogMap()
{
Table("BillOfSaleLog");
Id(x => x.FormId).Column("FormID").GeneratedBy.Native();
....
....
References<Form>(x => x.Form, "FormID").LazyLoad().ReadOnly();
}
}
public class FormMap : ClassMap<Form>
{
public FormMap()
{
Table("BillOfSaleForm");
Id(x => x.Id).Column("FormID").GeneratedBy.Foreign("Log");
...
...
HasOne<FormLog>(x => x.Log).Cascade.All();
HasMany(x => x.Buyers).KeyColumn("FormID").Inverse().Cascade.All();
HasMany(x => x.Sellers).KeyColumn("FormID").Inverse().Cascade.All();
}
}
public class BuyerMap : ClassMap<Buyer>
{
public BuyerMap()
{
Table("BillOfSaleBuyer");
CompositeId()
.KeyReference(x => x.Form, "FormID")
.KeyProperty(x => x.InnerOrderId, "tdNum1");
....
....
}
}
Seller is exectly the same
The problem occurs when I try to save the entity by the Edit Action
I'm using MVC to get the user data and make it an object.
The binding works fine and makes the object along with the collections.
But when I save an entity receive the following error:
I expect NHibernate be smart enough to set the Buyer & Seller foreign keys.
But in fact they remain without value.
The problem can be solved by setting foreign keys manually
as the following code:
//i have to set the form proerty in the childs
//without of the lines NH will try to save the childs it with FormID = null
foreach (var buyer in form.Buyers) { buyer.Form = form; }
foreach (var seller in form.Sellers) { seller.Form = form; }
But I'm looking for an elegant and correct solution
Thank you for reading
You don't show the code for adding a Buyer or Seller to a Form but it should look like this:
form.Buyers.Add(buyer);
buyer.Form = form;
Because Form is the inverse side of the relationships, you have to set the reference to Form on the many side. You should do this anyway so that the in-memory objects are correct. If the Buyer and Seller objects are already persistent in the same ISession than that should work.
According to this related question, HHibernate does not support cascade working with a composite key (as you have in BuyerMap). One of the answers does contain a hack to workaround this, but you will end up with a redundant column.
I have an object where I maintain a relationship to another object:
public class Foo {
public Bar Bar { get; set; }
}
In the mapping I reference Bar to that I can maintain the relationship - I don't want the parent object to update properties in the child, just the existence of the relationship:
References(x => x.Bar, "BarId")
.Cascade.None();
In the UI layer I create the relationship using a property which is not the underlying primary key:
item.Bar = new Bar { Code = "123" };
In the repository layer I hydrate the object if it doesn't have the primary key populated:
if(item.Bar.Id == null)
{
item.Bar = barRepository.RetrieveByCode(item.Bar.Code);
}
When I the RetrieveByCode line runs (which is a Criteria.UniqueResult under the covers) I get a TransientObjectException telling me that "the object references an unsaved transient instance - save the transient instance before flushing" for the Bar type.
When I run the same code path without creating the temporary Bar object it works. It appears that the Bar created as a temporary oject is tracked by NHibernate, yet I want it to forget that it ever existed as it is only a placeholder.
Any thoughts on how to achieve this?
UPDATE: Doing some more testing on this it seems to be the change tracking in Foo that is causing trouble. If I call Session.Evict(item) after retrieving it, but before making any changes and then re-attach the object using Session.Update(item) after I am done it seems to work, however it updates the child objects which is not what I want - I only want to manage the relationship.
UPDATE 2: I changed the FlushMode from Auto to Commit. It seems to have disabled the queueing of any interim changes to the object. Having researched NH behavior a bit further it seems that Update works more like a "re-attach" call rather than an explicit "update now" call.
UPDATE 3: It appears changing FlushMode caused other issues with transactions that required several operational steps. I reverted back to try another approach:
if(item.Bar.Id == null)
{
var barCode = item.Bar.Code;
item.Bar = null;
item.Bar = barRepository.RetrieveByCode(barCode);
}
Why do you want it to work that way? Why not simply set item.Bar using the retrieved Bar object:
item.Bar = barRepository.RetrieveByCode("123");
You might be able to make your current pattern work using Load:
if(item.Bar.Id == null)
{
var bar = barRepository.RetrieveByCode(item.Bar.Code);
item.Bar = session.Load<Bar>(bar.Id);
}