So, I have a list of children on my parent object, and I want to persist them on my SQL Server. When I run the application for the first time, all the children get their FK correctly, but when I run it again and no new parent is added, the new child(of an existing parent) doesn't get it's parent FK, just NULL. How can I map the parent FK on my child mapping for those situations?
I've tried the Inverse() method, but as I need the parent key to be generated all children gets null anyway. I need something like, if the parent is new, then the parent will update it's children FK, but when only the child is new I would need it to do the Inverse() method, is it possible?
Some more info:
Every time I call the ParentPersist method, and it cascades as needed. I've added the AddChild() method to set the ParentId when a new child is added to the list, it's working as I debugged it, so the child is setting it's ParentId correctly.
The objects are like the following:
public class Parent
{
public virtual int Id { get; set; }
...
public virtual IList<Child> Children{ get; set; }
public virtual void AddChild(Child ch)
{
ch.IdParent = this.Id;
Children.Add(ch);
}
}
public class Child
{
public virtual int Id { get; set; }
...
public virtual int IdParent {get;set;}
}
And my mapping:
public class ParentMapping : ClassMap<Parent>
{
public ParentMapping ()
{
Id(cso => cso.Id).GeneratedBy.Identity();
...
HasMany(cso => cso.Children).KeyColumn("IdParent").Cascade.SaveUpdate().Not.LazyLoad();
}
}
public class ChildMapping : ClassMap<Child>
{
public ChildMapping ()
{
Id(cso => cso.Id).GeneratedBy.Identity();
...
}
}
Your logic (e.g. Add() method in Parent, Inverse() mapping) was OK. You were almost there. There is only one BUT...
In general, the proper (if not only correct) solution is to use objects to express realtion and not just the ValueType/int values. That's why we call it ORM - Object-relational mapping
Object in C# should look like this:
public class Parent
{
...
// correct mapping of the children
public virtual IList<Child> Children{ get; set; }
// this method uses the below updated Child version
public virtual void AddChild(Child ch)
{
// this is replaced
// ch.IdParent = this.Id;
// with this essential assignment
ch.Parent = this;
Children.Add(ch);
}
}
public class Child
{
...
// instead of this
// public virtual int IdParent {get;set;}
// we need the reference expressed as object
public virtual Parent Parent { get; set; }
}
So, now, once we have objects in place, we can adjust the mapping like this:
// parent
public ParentMapping ()
{
...
HasMany(cso => cso.Children)
.KeyColumn("IdParent")
.Inverse() // this is essential for optimized SQL Statements
.Cascade.SaveUpdate() // All delete orphan would be better
.Not.LazyLoad();
}
...
// Child
public ChildMapping ()
{
...
References(x => x.Parent, "IdParent"); // it is a to use Inverse()
}
With this Business Domain Model and the mapping (Inverse(), assigning bothe relation ends in Add() method...), NHibernat will have enough information to always (insert, update) issue proper SQL statements
NOTE: One could ask why to map Parent Parent { get; set; } and not just the int IdParent { get; set; }... In fact, if we would have existing Parent (with NOT transient ID, i.e. > 0) - there won't be any difference. The trick/problems would appear on a new Parent insertion. Almost always, assignement of the children comes before the Parent is persiseted (flushed), and its ID is recieved from DB (sql server identity). And that could/would cause the child.IdParent == 0 ...
We should remember, that in general - ORM is about objects, i.e. relation is represented by Reference types.
Related
I have a small problem with a specific include statement.
My datastructure is as follows:
[Table("item")]
public class Item
{
public int Id { get; set; }
public string ItemCode { get; set; }
...
}
public abstract class DerivedItemAbstractBase : Item
{
[ForeignKey("ItemId")]
public List<Assignment> Assignments { get; set; }
...
}
[Table("item")]
public class DerivedItemA : DerivedItemAbstractBase
{
...
}
[Table("item")]
public class DerivedItemB : DerivedItemAbstractBase
{
...
}
public class ItemContext : DbContext
{
public DbSet<Item> Items { get; set; }
...
}
Now I want to get a list of all DerivedItemA and include properties of it.
I have the following method:
public List<DerivedItemA> GetDerivedItemsA()
{
var list = _context.Items
.Include(x => (x as DerivedItemA).Assignments)
.ToList();
}
This code compiles just fine and is something I have found on stackoverflow.
However executing this results in an exception with the short message Invalid include.
I dont know how to solve this problem.
The project is a database-first approach so I have no control over the database.
All items are stored in the same table item.
I cannot create multiple DbSets because there is no discriminator column in the table and I
cannot configure a custom discriminator in code because it would need to discriminate based on multiple properties and not a single property.
Is there any other way of doing this?
Currently I am solving it by iterating through all ItemContext.Items then doing a .Select() on each and creating a new DerivedItemA. After that I manually set every Assignment by iterating from the Assignment table.
However this approach takes far too long and it would be a lot quicker if I could just include it in the initial query.
I have two entities:
public class Parent
{
public virtual int Id { get; set; }
}
public class Child
{
public virtual int Id { get; set; }
public virtual int ParentId
{
get
{
if (Parent != null)
return Parent.Id;
return -1;
}
set
{
if (Parent != null)
Parent = new Parent();
Parent.Id = value;
}
}
protected virtual Parent Parent
{
get;
set;
}
}
The Parent property is set up like this to simplify the API side, and I do not want to change it to expose this property publicly. I have an override of the mappings for the Child class to accommodate this:
public class ChildMappingOverrides : IAutoMappingOverride<Child>
{
public void Override(AutoMapping<Child> mapping)
{
mapping.References<Parent>(Reveal.Member<Child>("Parent")).Column("Parent_id");
mapping.IgnoreProperty(x => x.ParentId);
}
}
Now, if I want to query all the Child objects for a given parent Id, I would perform this:
session.QueryOver<Child>().Where(c => c.ParentId == 1);
However, this throws a QueryException:
could not resolve property: ParentId of: My.Namespace.Child
How can I retrieve the set of Child objects that have a particular Parent Id?
Untested, but you could try this:
session.QueryOver<Child>()
.Where(Restrictions.Eq(
Projections.SqlProjection(
"{alias}.Parent_id as ParentId",
new[] { "ParentId" },
new[] { NHibernateUtil.Int32 }), 1))
.List<Child>();
You're not going to be able to query on unmapped associations in any NHibernate query, but this at least minimizes the loss of compile-time checking.
This is somewhat limited from what I can tell. {alias} will always be replaced by the alias of the root entity, meaning if you want to do this for a more complex query that doesn't start with Child, you might be out of luck.
I've solved it by using CreateSQLQuery instead of QueryOver:
session.CreateSQLQuery("SELECT C.* FROM CHILD C WHERE C.Parent_id = (:id)")
.AddEntity(typeof(Child))
.SetInt32("id", parentId)
.List<Child>();
I'd like to see a better way if possible, losing the compile-time checking is kind of a downer.
Using BsonClassMap, is it possible to map a domain object reference while keeping the domain object assembly persistent ignorant (changing the public A Reference { get; set; } property to public MongoDBRef Reference{ get; set; } in the sample class B below is not acceptable).
For this case, the referenced object is not a part of the same aggregate, and should not be stored as a nested document.
Is it possible map two domain objects in a relationship like this:
public class A
{
public Guid Id {get; private set; }
}
public class B
{
public Guid Id { get; private set; }
public A Reference { get; set; }
}
Into the following document structure:
// Collection for class A
{ _id: "11111111-1111-1111-1111-111111111111" }
// Collection class B
{
_id: "22222222-2222-2222-2222-222222222222",
reference_id: "11111111-1111-1111-1111-111111111111"
}
The mapping may look like:
BsonClassMap.RegisterClassMap<A>(cm =>
{
cm.MapIdProperty(c => c.Id)
.SetIdGenerator(new GuidGenerator())
.SetRepresentation(BsonType.String);
}
BsonClassMap.RegisterClassMap<B>(cm =>
{
cm.MapIdProperty(c => c.Id)
.SetIdGenerator(new GuidGenerator())
.SetRepresentation(BsonType.String);
// How do I map the B.Reference to a manual reference (like
// the sample doc structure above) or possibly as a DBRef?
}
So, without changing the model, how do I map the Reference property to object A, from object B as either a DBRef or as a manual references (as in my sample document structure above)?
Is this possible using BsonClassMap? Or in order to use BsonClassMap and keep my domain assembly persistent ignorant, do I need to change the model to something like:
public class A
{
public Guid Id {get; private set; }
}
public class B
{
public Guid Id { get; private set; }
public Guid ReferenceId { get; set; } // Don't reference the object directly,
// just store the Guid to the
// referenced object.
}
I posed this same question to the mongodb-csharp user group and got a response from craiggwilson:
You'll need to change your ReferenceProperty to ReferencePropertyId. We do not support lazy-loading (or eager-loading) of referenced documents.
Since A is not the aggregate for B, then this actually makes more sense when discussing in these terms. Generally, it is unnecessary for a referenced aggregate (B) to be loaded in order to process the referencing aggregate (A). It might be that you do indeed need some information from B. In this case, think about denormalizing a little and creating a true entity (BSummary) whose aggregate is A. This would make sense if some of the summary information is immutable or changes infrequently.
I'm trying to have a simple one to many relationship/hierarchy using NHibernate. I would like orphans to be deleted automatically but my current attempts to do so all result in an ObjectDeletedException. I'm wondering if someone can tell me what I'm doing incorrectly.
EDIT:
I should have specified that I'm loading a root Foo, then removing a child outside of the session, causing one or more children to be orphaned. The exception is occurring when I subsequently call SaveOrUpdate(root) in a second session. How can I rectify the difference in the list of children between the detached and modified object vs the object that is persisted in the database?
Sample code in question looks something like this:
Foo foo = new Foo();
Foo child1 = new Foo();
Foo child2 = new Foo();
foo.Children.Add(child1);
child1.Children.Add(child2);
// session #1
session.SaveOrUpdate(foo);
// so far, so good
// outside of any session
foo.Children.Clear();
// session #2
PutFoo(foo); // results in ObjectDeletedException
The object being persisted:
class Foo
{
private IList<Foo> children = new List<Foo> children;
public virtual int Id { get; private set; }
public IList<Foo> Children
{
get { return children; }
set { children = value; }
}
}
The FluentNHibernate mapping:
class FooMap : ClassMap<SyncDir>
{
public FooMap()
{
Id(x => x.Id);
base.HasMany(x => x.Children).Cascade.AllDeleteOrphan();
}
}
The method used to persist an object of type Foo:
void PutFoo(Foo foo)
{
using (var session = factory.OpenSession())
using (var transaction = factory.BeginTransaction())
{
session.SaveOrUpdate(foo);
transaction.Commit();
}
}
What I always do, is create a bidrectional relationship.
So, this means that the Children have a reference to their parent.
When removing a child from the collection, I also set the reference to the parent to NULL.
In the mapping , you then also have to indicate the 'inverse' end of the relationship.
I also never expose the collection 'as is' outside the class.
Thus, I mostly do it like this:
public class Foo
{
private ISet<Bar> _bars = new HashSet<Bar>();
public ReadOnlyCollection<Bar> Bars { get return new List<Bar>(_bars).AsReadOnly(); }
public void AddBar( Bar b )
{
b.Parent = this;
_bars.Add (b);
}
public void RemoveBar( Bar b )
{
b.Foo = null;
_bars.Remove (b);
}
}
public class Bar
{
public Foo Parent { get; set; }
}
Then, in the mapping I set the 'inverse' end on the collection.
So, this means that in my mapping (I still use xml files to specify the mapping), I set the inverse=true attribute on the Bars collection of the Foo class.
Is it possible to have a HasMany relationship of a basic type such as String, on an ActiveRecord class, without the need for creating another entity such as (TodoListItem) to hold the value.
[ActiveRecord]
public class TodoList
{
[PrimaryKey]
public int Id
{
get { return _id; }
set { _id = value; }
}
[HasMany(typeof(string)]
public IList<string> Items
{
get { return _items; }
set { _items= value; }
}
}
Can anyone help?
Yes, you can do this. You can map a one-to-many relation to a built-in or simple type (value type or string) rather than a persisted type.
You'll need to specify the ColumnKey, Table and Element params in the HasMany attribute declaration to get it to wire up properly. You have to have a surrogate key column so the AR can handle updates and cascades, and then Element tells AR which column in the table holds the simple value it will use to make the list.
[HasMany(typeof(string), Table="ToDoList_Items",
ColumnKey = "ListItemID", Element = "Item")]
public IList<string> Items { get; set; }
(or something similar - I haven't got a compiler handy on this box to check it; but per the API docs it ought to work.)
Speaking of which, if you haven't already had a look, http://api.castleproject.org is kinda indispensible for any work with the Castle stack.
In ActiveRecord, your types map to a record in a table (by default). It seems like you are confusing how this type should map to your table.
The MyClass type should have a definition something like this (excluding the PK settings):
[ActiveRecord(Table = "MyTable")]
public class MyClass : ActiveRecordBase<MyClass>
{
[Property]
public int Id { get; set; }
[Property]
public int MyClassId { get; set; }
[Property]
public string ListItem { get; set; }
}
Then, to load the list:
public void LoadMyClasses()
{
MyClass[] results = MyClass.FindAll();
}
I'd suggest you spend some time with the ActiveRecord documentation (or tutorial) as that should also help clear up any confusion.