Mapping a many-to-many relationship as an IDictionary - nhibernate

I am trying to map a Person and Address class that have a many-to-many relationship. I want to map the Address collection as an IDictionary with the Address property Type as the key. The relationship is only mapped from the Person side.
public class Person
{
public IDictionary<int, Address> Addresses { get; set; }
}
public class Address
{
public int Type { get; set; }
}
The mapping I am using is:
HasManyToMany<Address>(x => x.Addresses).Table("PersonAddress")
.ParentKeyColumn("PersonId").ChildKeyColumn("AddressId")
.AsMap(x => x.Type);
The problem is that the SQL issued is:
SELECT addressesd0_.PersonId as PersonId1_,
addressesd0_.AddressId as AddressId1_,
addressesd0_.Type as Type1_,
address1_.AddressId as AddressId5_0_
-- etc.
FROM dbo.PersonAddress addressesd0_
left outer join dbo.Address address1_
on addressesd0_.AddressId = address1_.AddressId
WHERE addressesd0_.PersonId = 420893
It's attempting to select Type from the many-to-many join table, which doesn't exist. I've tried a number of variations of the mapping without success.
How can I map this?

It's not possible. Dictionaries need a key value in the relational table. The table structure you have is a simple many-to-many bag or set.
What I would do, is map it as a normal bag or set and provide dictionary like access in the entity:
public class Person
{
private IList<Address> addresses;
public IEnumerable<Address> Addresses { get { return addresses; } }
public Address GetAddressOfType(int addressType)
{
return addresses.FirstOrDefault(x => x.Type == addressType);
}
public void SetAddress(Address address)
{
var existing = GetAddressOfType(address.Type);
if (existing != null)
{
addresses.Remove(existing);
}
addresses.Add(address);
}
}

You need to use components:
HasMany<Address>(x => x.Addresses)
.AsMap<int>("FieldKey")
.Component(x => x.Map(c => c.Id));

Related

Seeding Data - Owned Type without Id

I have an Entity describing an organization, including its postal address. The Address is stored in a property (PostalAddress) and all properties of Organization is stored and flattened in the same database table. The configuration compiles and the migration is applied without any problems - if I don't seed data. I have tested standard CRUD in Razor Pages - no problem.
When adding a Seed in OnModelCreating I get an error when compiling.
The seed entity for entity type 'Organization.PostalAddress#PostalAddress' cannot be added because no value was provided for the required property 'OrganizationId'.
The message confuses me, as neither Organization, nor PostalAddress have an OrganizationId property. Neither does a shadow property exist in the db. Any ideas of what causes the problem?
public abstract class BaseEntity<TEntity>
{
[Key] public virtual TEntity Id { get; set; }
}
public class MyOrganization : BaseEntity<long>
{
public string Name { get; set; } // Name of the Organization
public PostalAddress PostalAddress { get; set; } // Postal address of the Organization
public string Email { get; set; } // Email of the Organization
}
public class PostalAddress
{
public string StreetAddress1 { get; set; } // Address line 1
public string ZipCode_City { get; set; } // Zip code
public string Country { get; set; } // Country
}
public void Configure(EntityTypeBuilder<Organization> builder)
{
builder
.ToTable("Organizations")
.HasKey(k => k.Id);
// Configure PostalAddress owned entity
builder
.OwnsOne(p => p.PostalAddress, postaladdress =>
{
postaladdress
.Property(p => p.StreetAddress1)
.HasColumnName("StreetAddress1")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
postaladdress
.Property(p => p.ZipCode_City)
.HasColumnName("ZipCode_City")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
postaladdress
.Property(p => p.Country)
.HasColumnName("Country")
.HasColumnType("nvarchar(max)")
.IsRequired(false);
});
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder
.AddDBConfigurations();
// Seed data
builder
.Entity<Organization>(b =>
{
b.HasData(new Organization
{
Id = 1,
Name = "Test Organization",
Email = "nobody#nowhere.com"
});
b.OwnsOne(e => e.PostalAddress)
.HasData(new
{
StreetAddress1 = "1600 Pennsylvania Avenue NW", ZipCode_City = "Washington, D.C. 20500", Country = "USA"
});
});
}
The exception message is enigmatic, but helpful. When you add OrganizationId to the seeding code for PostalAddress it works.
modelBuilder
.Entity<Organization>(b =>
{
b.HasData(new Organization
{
Id = 1, // Here int is OK.
Name = "Test Organization",
Email = "nobody#nowhere.com"
});
b.OwnsOne(e => e.PostalAddress)
.HasData(new
{
OrganizationId = 1L, // OrganizationId, not Id, and the type must match.
StreetAddress1 = "1600 Pennsylvania Avenue NW",
ZipCode_City = "Washington, D.C. 20500",
Country = "USA"
});
});
Probably some undocumented convention is involved here. Which makes sense: the owned type can be added to more entity classes and EF needs to know which one it is. One would think that Id should be enough, after all, the type is clearly added to b, the Organization. I think some inner code leaks into the public API here and one day this glitch may be fixed.
Note that the type of the owner's Id must match exactly, while the seeding for the type itself accepts a value that has an implicit conversion.
it caused by convention if you need to make one to one relationship you need to add virtual property of MyOrganization and add organizationId in postal address object when seeding data this also help you to configure one to one relation https://www.learnentityframeworkcore.com/configuration/one-to-one-relationship-configuration

Mapping-By-Code ignores Column Name in BagPropertyMapper

I'm encountering a problem, and I think it's an NHibernate defect.
My Database Schema, containing a simple Parent-Child mapping:
TABLE Company
(
ID BIGINT PRIMARY KEY
)
TABLE CompanyMailRecipient
(
ID BIGINT PRIMARY KEY IDENTITY(1, 1),
Company_ID BIGINT NOT NULL FOREIGN KEY REFERENCES Company(id),
Name VARCHAR(MAX),
EmailAddress VARCHAR(MAX),
DestinationType TINYINT
)
My classes. Note that the CompanyMailRecipient table has a column called EmailAddress, but my MailRecipient class has a column called Address.
public enum MessageDestinationType
{
Normal = 1,
CC = 2,
BCC = 3
}
public class MailRecipient
{
public virtual string Name {get; set }
public virtual string Address {get; set; } // Different name to the column!
public virtual MessageDestinationType DestinationType {get; set;}
}
public class MailConfiguration
{
private Lazy<IList<MailRecipient>> _recipients = new Lazy<IList<MailRecipient>>(() => new List<MailRecipient>());
public virtual IList<MailRecipient> Recipients
{
get
{
return _recipients.Value;
}
set
{
_recipients = new Lazy<IList<MailRecipient>>(() => value);
}
}
}
public class Company
{
public virtual long Id { get; set; }
public virtual MailConfiguration MailConfiguration { get; set; }
}
The mapping code
mapper.Class<Company>(
classMapper =>
{
classMapper.Table("Company");
classMapper.Component(
company => company.MailConfiguration,
componentMapper =>
{
componentMapper.Bag(mc => mc.Recipients,
bagPropertyMapper =>
{
bagPropertyMapper.Table("CompanyMailRecipient");
bagPropertyMapper.Key(mrKeyMapper =>
{
mrKeyMapper.Column("Company_Id");
});
},
r => r.Component(
mrc =>
{
mrc.Property
(
mr => mr.Name,
mrpm => mrpm.Column("Name")
);
/*****************************/
/* Here's the important bit */
/*****************************/
mrc.Property
(
mr => mr.Address,
mrpm => mrpm.Column("EmailAddress");
);
mrc.Property
(
mr => mr.DestinationType,
mrpm => mrpm.Column("DestinationType")
);
};
)
);
}
}
Now here's the problem: when I attempt to query a Company, I get the following error (with significant parts in bold)
NHibernate.Exceptions.GenericADOException : could not initialize a collection: [Kiosk.Server.Entities.Company.MailConfiguration.Recipients#576][SQL: SELECT recipients0_.Company_Id as Company1_0_, recipients0_.Name as Name0_, recipients0_.Address as Address0_, recipients0_.DestinationType as Destinat4_0_ FROM CompanyMailRecipient recipients0_ WHERE recipients0_.Company_Id=?]
----> System.Data.SqlClient.SqlException : Invalid column name 'Address'.
at NHibernate.Loader.Loader.LoadCollection(ISessionImplementor session, Object id, IType type)
But, if I change my C# code so my MailRecipient class has a propery called EmailAddress instead of Address, everything works.
It's like NHibernate is ignoring my column mapping.
Is this an NHibernate bug, or am I missing something?
The version of NHibernate I'm using is 4.0.4.4.
The example above has a one-to-many component hanging off a component that hangs off the entity.
I found that if I removed a layer of inderection, and had my one-to-many component hanging off the entity directly, then the column name takes effect.
Yes, this was indeed a bug in NHibernate.
I issued a fix as a pull request which has now been merged into the codebase. It should be in a release after 4.1.1.
Bug NH-3913
GitHub Commit

Nhibernate query for items that have a Dictionary Property containing value

I need a way to query in Nhibernate for items that have a Dictionary Property containing value.
Assume:
public class Item
{
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
}
and mapping:
public ItemMap()
{
HasMany(x => x.DictionaryProperty)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.AsMap<string>(
index => index.Column("IDNumber").Type<int>(),
element => element.Column("TextField").Type<string>().Length(666)
)
.Cascade.AllDeleteOrphan()
.Fetch.Join();
}
I want to query all Items that have a dictionary value of "SomeText". The following example in Linq fails:
session.Query<Item>().Where(r => r.DictionaryProperty.Any(g => g.Value == "SomeText"))
with error
cannot dereference scalar collection element: Value
So is there any way to achieve that in NHibernate? Linq is not an exclusive requirement but its preffered. Not that I'm not interested to query over dictionary keys that can be achieved using .ContainsKey . Φορ this is similar but not the same
Handling IDictionary<TValueType, TValueType> would usually bring more issues than advantages. One way, workaround, is to introduce a new object (I will call it MyWrapper) with properties Key and Value (just an example naming).
This way we have to 1) create new object (MyWrapper), 2) adjust the mapping and that's it. No other changes... so the original stuff (mapping, properties) will work, because we would use different (readonly) property for querying
public class MyWrapper
{
public virtual int Key { get; set; }
public virtual string Value { get; set; }
}
The Item now has
public class Item
{
// keep the existing for Insert/Updae
public virtual IDictionary<int, string> DictionaryProperty {get; set;}
// map it
private IList<MyWrapper> _properties = new List<MyWrapper>();
// publish it as readonly
public virtual IEnumerable<MyWrapper> Properties
{
get { return new ReadOnlyCollection<MyWrapper>(_properties); }
}
}
Now we will extend the mapping:
HasMany(x => x.Properties)
.Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore)
.Component(c =>
{
c.Map(x => x.Key).Column("IDNumber")
c.Map(x => x.Value).Column("TextField")
})
...
;
And the Query, which will work as expected:
session
.Query<Item>()
.Where(r =>
r.Properties.Any(g => g.Value == "SomeText")
)
NOTE: From my experience, this workaround should not be workaround. It is preferred way. NHibernate supports lot of features, but working with Objects brings more profit

NHibernate table per type persist child from existing parent

I have 2 classes, Member.cs and Customer.cs and using table-per-type inheritance mapping described here.
This question poses the same problem, but with no answer.
Customer.cs
public class Customer
{
}
Member.cs
public class Member : Customer
{
public Member(Customer customer)
{
CreateFromCustomer(customer);
}
private void CreateFromCustomer(Customer customer)
{
// Here I assume I'll assign the Id so NHibernate wouldn't have to create a new Customer and know what Customer to be referred
Id = customer.Id;
}
}
CustomerMap.cs
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
}
}
MemberMap.cs
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
KeyColumn("Id");
}
}
I tried several test case :
Test1.cs
[Test]
public void CanAddCustomer()
{
var customerRepo = /* blablabla */;
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
var customer = new Customer()
customerRepo.RegisterCustomer(customer);
tx.Commit();
}
using (var tx = NHibernateSessionManager.GetSession().BeginTransaction())
{
/* Get the persisted customer */
var customer = customerRepo.GetCustomerByWhatever();
var member = customerRepo.RegisterMember(new Member(customer));
tx.Commit();
}
}
I'm expecting to have :
1 customer and 1 member which is a child of that customer
Instead I have :
2 customers (1 that is what was correctly created and 1 with all null columns) and 1 member that Id referred to all null columns Customer.
Is it the expected behavior?
I understand if we were wanted to create a child object from a transient parent object, this is a correct behavior.
But what if we were to create a child object that refers to an existing parent object?
The link I provided doesn't cover any persistence example, neither does googling.
Short Answer
No, it is not possible to "upgrade" an already persisted object to its subclass. Nhibernate simply doesn't support this. That's why you see 2 customers and one member entry. This is actually the expected behavior because Nhibernate simply creates a copy with a new ID of the object instead of creating the reference to Member...
So basically you could do either
Copy the data of Customer into Member, delete customer and save Member
Use a different object structure without subclasses where Member is a different table with it's own ID and a reference to Customer
Use native sql to insert the row into Member...
Some example:
Your classes could look like this
public class Customer
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
public class Member : Customer
{
public virtual string MemberSpecificProperty { get; set; }
}
Basically, Member could have additional properties, but will have the same properties as Customer of cause, too.
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id)
.GeneratedBy.GuidComb();
Map(x => x.Name);
}
}
and for the sub class, you have to map additional properties only!
public class MemberMap : SubclassMap<Member>
{
public MemberMap()
{
Map(x => x.MemberSpecificProperty);
}
}
testing it
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Save(new Member()
{
Name = "Customer B",
MemberSpecificProperty = "something else"
});
session.Flush();
}
This will create 2 entries in the customer table and one row into Member table. So this is as expected, because we created one customer and one member...
Now the "upgrade" from Customer A to Member:
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
session.Save(new Customer()
{
Name ="Customer A"
});
session.Flush();
}
using (var session = NHibernateSessionFactory.Current.OpenSession())
{
var customer = session.Query<Customer>().FirstOrDefault();
//var member = customer as Member;
var member = new Member()
{
Name = customer.Name,
MemberSpecificProperty = "something else"
};
session.Delete(customer);
session.Save(member);
session.Flush();
}

nHibernate mapping by code - how to populate object tree with nested collections

I can't seem to find a good example of what I want to do, using nHibernate mapping by code:
I have an object "Message" that has a list of "Organisms" and each "Organism" has a list of "Drugs". Please forgive my pseudo example below:
public class Message
List<Organism> Organisms;
public class Organism
List<Drugs> Drugs;
public class Drug
//create our tree structure
var message=new Message();
var drug = new Drug();
var organism = new Organism();
organism.Drugs.Add(drug);
message.Organisms.Add(organism); //now we have a message with one organism child with one drug drug
Using Bags in my class mappings, I am able to correctly save this message object, and have it persist correctly. The problem is when calling Get with the message ID to bring back the message. I am getting "collection is not associated with any session"
As for my mappers, my message has a bag of organisms, which have a bag of drugs.
Does anyone have an example of doing this type of thing with nHibernate mapping by code? I am missing something in my mappers...
you need to do a little more if you want bi-directional mappings.
So I would define a message property on the Organism class. And map that as a reference.
Add Add/Remove properties on the parent class for your collection class so that you can add the element to the list and to also add the this reference to your child object.
public class Message
{
public Int32 Id { get; set; }
public IList<Organism> Organisms { get; protected set; }
public Message()
{
Organisms = new List<Organism>();
}
public void AddOrganism(Organism organism)
{
if (Organisms.Contains(organism))
return;
organism.Message = this;
Organisms.Add(organism);
}
public void RemoveOrganism(Organism organism)
{
if (!Organisms.Contains(organism))
return;
Organisms.Remove(organism);
}
}
public class Organism
{
public Int32 Id {get;set;}
public Message Message { get; set; }
}
With regards to your mappings you need to set the Message.Organisms to a Bag and on the Organism.Message to a ManyToOne. For the next level down, just repeat this.
public class MessageMap
{
public MessageMap()
{
Bag(x => x.Organisms, map =>
{
map.Key(k =>
{
k.Column(col => col.Name("MessageId"));
});
map.Cascade(Cascade.All | Cascade.DeleteOrphans);
},
action => action.OneToMany());
}
}
public class OrganismMap
{
public OrganismMap()
{
ManyToOne(x => x.Message, map =>
{
map.Column("MessageId");
map.NotNullable(false);
});
}
}