I'm building an application which uses NHibernate mapping by code, and I am unable to map protected properties when I use a component mapping (equivalent to hbm composite-element mapping) for a collection of value objects.
I am able to map protected properties in entity and compoment mappings for single value objects, it is just protected properties do not appear to be supported when mapping collections of value objects.
public class MyEntity
{
public virtual int Id { get; protected set; }
protected virtual MyValueObject MyValueObject { get; set; }
}
public class MyValueObject
{
protected string SomeString { get; set; }
protected ISet<NestedValueObject> NestedValueObjects { get; set; }
// Constructor, Equals/GetHashcode overrides, etc.
}
public class NestedValueObject
{
public string Name { get; set; }
protected DateTime CreatedOn { get; set; } // Audit only property
// Constructor, Equals/GetHashcode overrides, etc.
}
public MyEntityMap()
{
Table("MyEntityTable");
Id(x => x.Id, map =>
{
map.Column("Id");
});
Component<MyValueObject>("MyValueObject", c =>
{
// Protected property maps correctly
c.Property("SomeString", map =>
{
map.NotNullable(true);
});
c.Set<NestedValueObject>("NestedValueObjects", map =>
{
map.Table("NestedValueObjectsTable");
map.Key(k => k.Column("MyEntityId"));
}, r => r.Component(n =>
{
// Public property maps correctly
n.Property(x => x.Name);
// Compilation fail - there is no method that supports protected properties in component mappings
n.Property<DateTime>("CreatedOn", map =>
{
map.NotNullable(true);
});
}));
});
}
This is because IMinimalPlainPropertyContainerMapper<TContainer> supports protected properties, while IComponentElementMapper<TComponent> doesn't.
Is there a reason for this? It seems reasonable that a value object should be allowed to have protected properties which are for auditing purposes only and do not form a part of its conceptual identity, and protected properties are supported with the component mapping for single value objects.
It looks like this is missing feature, rather than a design decision, and will be fixed in a future release of NHibernate:
https://nhibernate.jira.com/browse/NH-3993
As a workaround until this release, the alternatives would be to make the properties public or to map the value object as an entity with a composite id using a one-to-many mapping, since these support protected variables.
Related
I'm using asp.net core on a project. (I'm fairly new to it)
I have a User Model. the code below is a simplified version:
public class User
{
public int id { get; set; }
// attribute declaration
public ICollection<User> friends { get; set; }
}
I'm using automapper service to map my api to this Model:
public class UserResource
{
public UserResource()
{
this.friendsId = new List<int>();
}
public int id { get; set; }
// attribute declaration
public ICollection<int> friendsId { get; set; }
}
consider a post request to UserController with the following body:
{
"id" : 1
"friendsId": [2,3,4],
}
I want to map integers in friendsId to id of each user in friends collection. but I can't figure out what to do. here's what I've got:
CreateMap<UserResource,User>()
.ForMember(u => u.friends,opt => opt.MapFrom(????);
is this the right approach? if so how should I implement it?
or should I change my database model to this:
public class User
{
public int id { get; set; }
// attribute declaration
public ICollection<int> friendsId { get; set; }
}
Thank you in advance.
You'll need to implement a custom value resolver. These can be injected into, so you can access things like your context inside:
public class FriendsResolver : IValueResolver<UserResource, User, ICollection<User>>
{
private readonly ApplicationDbContext _context;
public FriendsResolver(ApplicationDbContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
}
public ICollection<User> Resolve(UserResource source, User destination, ICollection<User> destMember, ResolutionContext context)
{
var existingFriendIds = destMember.Select(x => x.Id);
var newFriendIds = source.friendsId.Except(existingFriendIds);
var removedFriendIds = existingFriendIds.Except(source.Friends);
destMember.RemoveAll(x => removedFriendIds.Contains(x.Id);
destMember.AddRange(_context.Users.Where(x => newFriendIds.Contains(x.Id).ToList());
return destMember;
}
}
Not sure if that's going to actually work as-is, as I just threw it together here, but it should be enough to get your going. The general idea is that you inject whatever you need into the value resolver and then use that to create the actual stuff you need to return. In this case, that means querying your context for the User entities with those ids. Then, in your CreateMap:
.ForMember(dest => dest.friends, opts => opts.ResolveUsing<FriendsResolver>());
This only covers one side of the relationship, though, so if you need to map the other way, you may need a custom resolver for that path as well. Here, I don't think you actually do. You should be able to just get by with:
.ForMember(dest => dest.friendsId, opts => opts.MapFrom(src => src.friends.Select(x => x.Id));
This would help
CreateMap<UserResource,User>()
.ForMember(u => u.friends,opt => opt.MapFrom(t => new User {FriendsId = t.friendsId);
public class User
{
...
public ICollection<User> friends { get; set; }
}
Where friends is ICollection<User> whereas UserResource class has ICollection<int>. There is type mismatch here. You need to map ICollection to ICollection that is why I casted new User ...
I'm converting from Fluent to Loquacious, and I've run in to an issue where my interceptors are not getting all the fields like I think they should. If I look at the OnSave function
public override Boolean OnSave(Object entity, Object id, Object[] state,
String[] propertyNames, IType[] types)
and take a look at the propertyNames the only items in there are the items that were explicitly mapped in the mapping file (in the example this would just be ID, Start, and End).
In my case though I have a base class which isn't mapped at all. Instead it's just contains properties that get filled out by the interceptors. This used to work in Fluent Nhibernate, but now that I've moved to Nhibernate 3.3 I can't get it to work anymore.
My classes/mapping look something like this
public class BaseAuditEntity
{
public virtual int ModifiedByUserID { get; set; }
public virtual DateTime LastModifiedTime { get; set; }
}
public class Foo : BaseAuditEntity
{
public virtual int ID { get; protected internal set; }
public virtual DateTime Start { get; protected internal set; }
public virtual DateTime End { get; protected internal set; }
}
public class FooMap: ClassMapping<Foo>
{
Id(x => x.ID, m => m.column("fooID"));
Property(x => x.Start, m => m.column("start"));
Property(x => x.End, m => m.column("end"));
}
Any ideas of how to get this work? I don't want to have to map this every class, and I didn't think I needed to map the BaseAuditEntity, at least with Fluent it wasn't needed.
you could make a base mapping class
public class BaseAuditEntityMapping<T> : ClassMapping<T> where T: BaseAuditEntity
{
ManyToOne(x => x.ModifiedByUser);
Property(x => x.LastModifiedTime);
}
public class FooMap: BaseAuditEntityMapping<Foo>
I have a class that is many-to-one with its parent. I'd like to expose the parent's properties through the child without exposing the parent directly. I'd also like to query on and order by those properties.
Classes
public class Organization
{
public virtual string Name { get; set; }
public virtual bool IsNonProfit { get; set; }
}
public class Contact
{
private Organization _organization;
public virtual string OrganizationName
{ get { return _organization.Name; } }
public virtual bool OrganizationIsNonProfit
{ get { return _organization.IsNonProfit; } }
}
Mapping
public class OrganizationMap : ClassMap<Organization>
{
public OrganizationMap()
{
Map(x => x.Name);
Map(x => x.IsNonProfit);
}
}
public class ContactMap : ClassMap<Contact>
{
public ContactMap()
{
References<Organization>(Reveal.Member<Contact>("_organization"))
.Access.CamelCaseField();
}
}
Query
public class Example
{
private ISessionFactory _sessionFactory;
public Example(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
public IEnumerable<Contact> DoQuery(int forPage, int rowsPerPage)
{
using (var session = _sessionFactory.OpenSession())
{
return session.Query<Contact>().OrderBy(x => x.OrganizationName)
.Skip((forPage - 1) * rowsPerPage).Take(rowsPerPage);
}
}
}
The problem is that this results in a "Could not resolve property: OrganizationName" error. It looks like I could map those fields with a formula, but then I'd end up with a sub-select for each field on a table that's already joined into my query. Alternatively, I could wrap the Contact's organization with a public getter and change my query to OrderBy(x => x.Organization.Name). That leaves me with a Law of Demeter violation though.
Am I off track? How should I handle this?
edited to show paging
You can't use non-mapped properties in queries. How should NHibernate know how to create a SQL condition for it? In your case it might be easy, but what if you'd have a call to a method or some complicated logic in that property?
So yes, you need at least a public getter property.
Alternatively, do the sorting in memory (after NHibernate has executed the query).
I need to persist this class on database using Fluent NHibernate:
public class RaccoonCity
{
public virtual int Id { get; private set; }
public virtual DateTime InfectionStart { get; private set; }
private IList<Zombie> _zombies = new List<Zombie>();
public virtual IEnumerable<Zombie> Zombies
{
get { return _zombies; }
}
protected RaccoonCity()
{}
public RaccoonCity(DateTime startMonth)
{
InfectionStart = startMonth;
}
public virtual void AddZombie(Zombie z)
{
_zombies.Add(z);
}
}
The property has type IEnumerable to indicate that you shouldn´t use it to insert new items. The backing field is of IList to make it easy to insert new items from the own class.
Zombie is a simple class:
public class Zombie
{
public virtual int Id { get; private set; }
public virtual string FormerName { get; set; }
public virtual DateTime Infected { get; set; }
}
The map is the following:
public class RaccoonCityMap: ClassMap<RaccoonCity>
{
public RaccoonCityMap()
{
Id(x => x.Id);
Map(x => x.InfectionStart);
HasMany(x => x.Zombies)
.Access.CamelCaseField(Prefix.Underscore)
.Inverse()
.Cascade.All();
}
}
When I test this, the data is inserted in database, but the zombie´s foreign keys are empty, and the RaccoonCity instance has zero items on Zombies list.
You are declaring the relationship as Inverse, which means the Zombie and not the RacoonCity is responsible for maintaining the relationship.
Either add the corresponding reference to zombie and set it on the AddZombie method, or remove the Inverse (in that case, you'll see an INSERT with a null FK followed by an update).
Suggested reading: http://nhibernate.info/doc/nh/en/index.html#collections-onetomany
Found a post about it: https://web.archive.org/web/20090831052429/http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/08/15/a-fluent-interface-to-nhibernate-part-3-mapping.aspx
I had to implement the method
HasManyComponent by myself since it
was missing in the actual trunk of the
framework. That is, it was not
possible to map a collection of value
objects. But it has not been that hard
since the source base is really nice.
My changes will probably be integrated
into the framework soon.
And this one:
http://nhforge.org/blogs/nhibernate/archive/2008/09/06/a-fluent-interface-to-nhibernate-part-3-mapping-relations.aspx
What is the best way of mapping a simple Dictionary property using Fluent NHibernate?
public class PersistedData
{
public virtual IDictionary<key, value> Dictionary { get; set; }
}
public class PersistedDataMap : ClassMap<PersistedData>
{
HasMany(x => x.Dictionary)
.Table("dict_table")
.KeyColumn("column_id")
.AsMap<string>("key")
.Element("value");
}
This will properly map Dictionary to table dict_table and use column_id to associate it to the base id.
As a side note, if you would like to use an Enum as the Key in the dictionary, it should be noted that NHibernate.Type.EnumStringType<MyEnum> can be used in place of the string in .AsMap<string> to use the string value instead of the Ordinal.
Using a simple class relationship such as the following:
public class Foo {
public virtual IDictionary<string, Bar> Bars { get; set; }
}
public class Bar {
public virtual string Type { get; set; }
public virtual int Value { get; set; }
}
You can map this with Fluent NHibernate in this way:
mapping.HasMany(x => x.Bars)
.AsMap(x => x.Type);
Where Bar.Type is used as the key field into the dictionary.
To map a list as a dictionary:
HasMany(x => x.Customers)
.AsMap();
I have not used it; so cannot give an example.
Have look at the wiki: Cached version of the page, Actual page I have given the cached version of the page as the site seems to be down.