NHibernate: map multiple columns into a single collection - nhibernate

Suppose I have a table:
ID(pk) | HOME_EMAIL | WORK_EMAIL | OTHER_EMAIL
-------------------------------------------------
and the .NET classes
class A {
int id;
List<MyEmail> emails;
}
class MyEmail {
string email;
}
I suppose there's no way to map those (multiple) columns into a single collection in NHibernate, or is there? :)
It's come to a point that we'd rather not tinker with the database schema anymore so we can't do much with the database, if that helps.

I would suggest working with Interfaces so you could do something like this
interface IUser
{
int Id {get; set;}
IEnumerable<string> Emails {get;}
}
class MyUser : IUser
{
public int Id {get; set;}
public IEnumerable<string> Emails
{
get
{
return new [] { SomeEmail, SomeOtherEmail };
}
}
public string SomeEmail { get; set; }
public string SomeOtherEmail { get; set; }
}
Your application can expect an IUser and not care where we got the list of emails. You would map MyUser in NH, while the application does not (and should not) care about the actual implementation.

If it doesn't have to be a collection, but could be a custom type instead, say EmailAddresses which contains three properties:
public class EmailAddresses
{
public virtual string Home { get; set; }
public virtual string Work { get; set; }
public virtual string Other { get; set; }
}
You could use a component to map the three columns into the three properties of this object as a single property on the parent:
public class MyUser
{
...
public virtual EmailAddresses { get; set; }
}
You can map these in NHibernate using components or if you're using Fluent NHibernate with the ComponentMap<T> classmap (automapper can't do components).

There is a feature that's very close to what you want, <dynamic-component>
The documentation at http://nhibernate.info/doc/nh/en/index.html#components-dynamic should get you started.

Related

Entity Framework Core - one-to-many but parent also has navigation property to a single child?

I currently have a working one-to-many relationship between the entities 'Conversation' and 'Message', where a conversation can have multiple messages.
This works fine:
public class Conversation
{
public long ID { get; set; }
}
public class Message : IEntity
{
public virtual Conversation Conversation { get; set; }
public long ConversationID { get; set; }
public long ID { get; set; }
}
However, I am trying to add a navigation property to the 'Conversation' class called 'LastMessage' which will keep track of the last message record that was created:
public class Conversation
{
public long ID { get; set; }
public virtual Message LastMessage { get; set; }
public long LastMessageID { get; set; }
}
When I try to apply the above, I get the error
System.InvalidOperationException: The child/dependent side could not
be determined for the one-to-one relationship between
'Conversation.LastMessage' and 'Message.Conversation'.
How do I maintain a one-to-many relationship between 'Conversation' and 'Message', but ALSO add a navigation property in the 'Conversation' class that navigates to a single 'Message' record?
If conversation can have several messages it is called one-to-many relations.
You have to fix the tables:
public class Conversation
{
[Key]
public long ID { get; set; }
[InverseProperty(nameof(Message.Conversation))]
public virtual ICollection<Message> Messages { get; set; }
}
public class Message
{
[Key]
public long ID { get; set; }
public long ConversationID { get; set; }
[ForeignKey(nameof(ConversionId))]
[InverseProperty("Messages")]
public virtual Conversation Conversation { get; set; }
}
After trying all sorts of Data Annotations and Fluent API nonsense, the cleanest solution I could come up with turned out to be very simple which requires neither. It only requires adding a 'private' constructor to the Conversation class (or a 'protected' one if you're using Lazy Loading) into which your 'DbContext' object is injected. Just set up your 'Conversation' and 'Message' classes as a normal one-to-many relationship, and with your database context now available from within the 'Conversation' entity, you can make 'LastMessage' simply return a query from the database using the Find() method. The Find() method also makes use of caching, so if you call the getter more than once, it will only make one trip to the database.
Here is the documentation on this ability: https://learn.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services
Note: the 'LastMessage' property is read-only. To modify it, set the 'LastMessageID' property.
class Conversation
{
public Conversation() { }
private MyDbContext Context { get; set; }
// make the following constructor 'protected' if you're using Lazy Loading
// if not, make it 'private'
protected Conversation(MyDbContext Context) { this.Context = Context; }
public int ID { get; set; }
public int LastMessageID { get; set; }
public Message LastMessage { get { return Context.Messages.Find(LastMessageID); } }
}
class Message
{
public int ID { get; set; }
public int ConversationID { get; set; }
public virtual Conversation Conversation { get; set; }
}

How to map and reference entities from other data sources with NHibernate

I'm currently working on and ASP.NET MVC application in which I have a User entity like follows:
public class User
{
public virtual int Id { get; protected set; }
public virtual string Name { get; protected set; }
public virtual string Role { get; protected set; }
public virtual Location Location { get; protected set; }
}
Where location is just as straightforward:
public class Location
{
public virtual string Id { get; protected set; }
public virtual string Building { get; protected set; }
public virtual string City { get; protected set; }
public virtual string Region { get; protected set; }
}
My complication arises because I want to populate the User from Active Directory and not the database. Additionally, several classes persisted to the database reference a user as a property. I've got an ADUserRepository for retrieval, but I don't know how to integrate these Users into my object graph when the rest is managed by NHibernate.
Is there a way for NHibernate to persist just an id for a User without it being a foreign key to a Users table? Can I map it as a component to accomplish this? I've also looked at implementing IUserType to make the translation. That way it would map to a simple field and ADUserRepository could be put in the chain to resolve the stored Id. Or am I trying to hack something that's not really feasible? This is my first time around with NHibernate so I appreciate any insight or solutions you can give. Thanks.
Update
It appears my best solution on this will be to map the User with an IUserType and inject (preferably with StructureMap) a service for populating the object before its returned. Framed in that light there are a couple of questions here that deal with the topic mostly suggesting the need for a custom ByteCodeProvider. Will I still need to do this in order for IUserType to take a parameterized constructor or do the comments here: NHibernate.ByteCode.LinFu.dll For NHibernate 3.2 make a difference?
using a Usertype to convert user to id and back
public class SomeClass
{
public virtual string Id { get; protected set; }
public virtual User User { get; protected set; }
}
// in FluentMapping (you have to translate if you want to use mapping by code)
public SomeClassMap()
{
Map(x => x.User).Column("user_id").CustomType<UserType>();
}
public class UserType : IUserType
{
void NullSafeSet(...)
{
NHibernateUtil.Int32.NullSafeSet(cmd, ((User)value).Id, index);
}
void NullSafeGet(...)
{
int id = (int)NHibernateUtil.Int32.NullSafeGet(cmd, ((User)value).Id, index);
var userrepository = GetItFromSomeWhere();
return userrepository.FindById(id);
}
}

MVC3 - Extending a Class and Updating the SQL Table

I am using MVC3 and Entity Framework. I have a class called User with 20 different properties. I have already created a database and filled it with some data. I want to break out the Addresses property and make it it's own class.
namespace NameSpace.Domain.Entities
{
public class User
{
public int UserId { get; set; }
...
...
public string AddressOne { get; set; }
public string AddressTwo { get; set; }
}
}
I want to break out both Addresses like so
namespace NameSpace.Domain.Entities
{
public class User
{
public int UserId { get; set; }
...
...
public Addresses Addresses { get; set; }
}
public class Addresses
{
public string AddressOne { get; set; }
public string AddressTwo { get; set; }
}
}
HERE'S MY QUESTION:
Since I already have the data table filled with data, how can I update this in the Server Explorer?
Thanks ( if you need more info please let me know )
If you are using EF code first 4.3 you can use the concept of migrations to achive what you want.
You will need to do a code based manual migration since you change is a bit to advanced for the framework to figure it out itselfe.
Further reading: http://blogs.msdn.com/b/adonet/archive/2012/02/09/ef-4-3-code-based-migrations-walkthrough.aspx

FluentNhibernate many-to-many and Inverse()

I have the following database tables defined:
Club: Id, Name
Member: Id, Name
ClubMember: ClubId, MemberId
I have the following entity Classes defined:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Member> Members { get; set; }
}
public class Member() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Club> Clubs { get; set; }
}
I have the following overrides defined:
public class MemberOverride : IAutoMappingOverride<Member>
{
public void Override(AutoMapping<Member> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Clubs)
.ParentKeyColumn("MemberId")
.ChildKeyColumn("ClubId")
.Cascade.All()
.Table("ClubMembers");
}
}
public class ClubOverride : IAutoMappingOverride<Club>
{
public void Override(AutoMapping<Club> mapping_)
{
mapping_
.HasManyToMany(x_ => x_.Members)
.ParentKeyColumn("ClubId")
.ChildKeyColumn("MemberId")
.Inverse()
.Table("ClubMembers");
}
}
I can see from my overrides that the Inverse on the ClubOverride means you cannot do the following
session.Save(club.Members.Add(member));
but this works:
session.Save(member.Clubs.Add(club);
But it doesn't make logical sense. I want to be able to save either the club with members or member with clubs.
Am I trying to do something impossible with FluentNhibernate?
TIA
Yes, you're right, that's not possible. But it's not a question of FluentNhibernate, NHibernate works like that.
Only one side is the owner of the relation and on charge of adding elements.
From official documentation:
Changes made only to the inverse end of the association are not persisted. This means that NHibernate has two representations in memory for every bidirectional association, one link from A to B and another link from B to A. This is easier to understand if you think about the .NET object model and how we create a many-to-many relationship in C#:
You can create add or remove methods on your entities that will help accomplish this:
public class Club() {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
private IList<Member> members;
public virtual IEnumerable<Member> Members { get { return members.Select(x => x); } }
public Club() {
members = new List<Member>();
}
public virtual void AddMember(Member member){
if (members.Contains(member))
return;
members.Add(user);
member.AddClub(this);
}
public virtual void RemoveMember(Member member){
if (!members.Contains(member))
return;
members.Remove(member);
member.RemoveClub(this);
}
}

FluentNHibernate mapping syntax help needed

I'm having some trouble figuring out the appropriate FluentNHibernate mapping syntax for the following data model and domain objects. Here's the data model I'm working against:
And I'm trying to map the following domain objects to that model:
namespace FluentNHibernateSandbox.Entities
{
public abstract class EntityBase
{
public virtual long Id { get; set; }
}
}
namespace FluentNHibernateSandbox.Entities
{
public class Attribute : EntityBase
{
public virtual string Name { get; set; }
public virtual string Label { get; set; }
public virtual string Description { get; set; }
public virtual int SortOrder { get; set; }
public virtual Group Group { get; set; }
public virtual Editor Editor { get; set; }
}
}
namespace FluentNHibernateSandbox.Entities
{
public class Group : EntityBase
{
public virtual string Name { get; set; }
public virtual string Label { get; set; }
public virtual string Description { get; set; }
public virtual int SortOrder { get; set; }
public virtual IList<Attribute> Attributes { get; set; }
}
}
namespace FluentNHibernateSandbox.Entities
{
public class Editor : EntityBase
{
public virtual string ViewName { get; set; }
public virtual string WorkerClassName { get; set; }
}
}
In general, what I ultimately want doesn't seem like it should be all that hard to do, but I after having tried just about every combination of mappings I can think of, I still can't seem to get it right. I just need my Attribute to have a reference to the Group that it belongs to and a reference to the Editor assigned to it, and each Group should have a collection of the Attributes that are part of it. The couple of many-to-many join tables are what seem to be giving me fits. Particularly the APPLICATION_ATTRIBUTE table. Ultimately I only want the Attributes that my application is concerned with, in this case, those with an APPLICATION_ID of 4.
Any help would be greatly appreciated. Thanks.
Really kinda surprised nobody responded to this at all, but anyway. The answer/solution for this mapping situation that we came up with, which I was trying to avoid to start with, but turned out to really be the best way to go, was to create some custom views in the database that joined together all of the application-specific data I needed, and then just mapped my application's domain objects to those views. This worked at least partially because the information I needed from these tables is going to be read-only for this application, but even if I needed to write to the tables, I'm pretty sure (though haven't verified as I didn't really have need in this case) that I could have setup my views to be writeable and that would've worked too.
Hat tip to #robconery.