I'm trying to map a collection of enum values with Fluent NHibernate.
IList<EnumType> lst;
I can't find any documentation about it but I'm quite sure it should be possible.
I had no problem at all with mapping a collection of Entities.
Thanks,
Leonardo
You can use the following FNH mapping signature to map simple value type collections.
HasMany(x => x.Collection)
.Table("TableName")
.KeyColumn("KeyColumnName")
.Element("ValueColumnName");
Where:
Collection: the collection of value types (can be enum because that will be mapped as int).
TableName: the name of the table that will store your collection values.
KeyColumnName: the name of the column that will store the key value back to the parent.
ValueColumnName : the name of the column that will store the actual value.
Lets see an example of how to map a few collections of value types.
public enum EnumType
{
Value1,
Value2,
Value3
}
public class Entity
{
/// <summary>
/// Primary key
/// </summary>
public virtual int Id { get; set; }
/// <summary>
/// Collection of strings
/// </summary>
public virtual IList<string> StringCollection { get; set; }
/// <summary>
/// Collection of enums
/// </summary>
public virtual IList<EnumType> EnumCollection { get; set; }
/// <summary>
/// Collection of dates/times
/// </summary>
public virtual IList<DateTime> DateTimeCollection { get; set; }
}
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
// Map primary key.
Id(x => x.Id);
// Map value collections
HasMany(x => x.StringCollection)
.Table("Entity_String")
.KeyColumn("EntityId")
.Element("String");
HasMany(x => x.EnumCollection)
.Table("Entity_Enum")
.KeyColumn("EntityId")
.Element("Enum");
HasMany(x => x.DateTimeCollection)
.Table("Entity_DateTime")
.KeyColumn("EntityId")
.Element("DateTime");
}
}
The result of this mapping will produce four (4) tables.
Table Entity with one column Id of int.
Table Entity_String with two columns - EntityId : int, String : varchar, and a foreign key EntityId to Entity table Id column.
... likewise, except column is of int type.
... likewise, except column is of datetime type.
HasMany(x => x.Items)
.Table("tbl")
.KeyColumn("fk")
.Element("eCol")
.AsBag()
Related
I am using the newest version of ASP.Net Boilerplate (version 3.7.2). I am currently using the new Multi-Lingual Entities.
I am having issues with the Automapping for 1 of the Entities to a Dto as not only does it require the Multi-Lingual aspect but a Projection/Custom Resolver for one of the properties.
Up to this point all the mappings have been working correctly. I have followed the documentation found at ASP.NET Boilerplate Documentation
[Table("CategoryItems")]
public class CategoryItem : BaseClass, IMultiLingualEntity<CategoryItemTranslation>
{
/// <summary>
/// Gets or Sets the Category Item Display Order
/// </summary>
public int DisplayOrder { get; set; }
/// <summary>
/// Gets or Sets Catalog Categories
/// </summary>
public virtual ICollection<CatalogItem> CatalogItems { get; set; } = new HashSet<CatalogItem>();
...
/// <summary>
/// Gets or Sets all the Entity Translations
/// </summary>
public virtual ICollection<CategoryItemTranslation> Translations { get; set; } = new HashSet<CategoryItemTranslation>();
}
[Table("CategoryItemTranslations")]
public class CategoryItemTranslation : IEntityTranslation<CategoryItem>
{
/// <summary>
/// Get or Set Category Item Name
/// </summary>
public string CategoryItemName { get; set; }
/// <summary>
/// Gets or Sets Link to Category Item
/// </summary>
public CategoryItem Core { get; set; }
/// <summary>
/// Gets or Sets Link to Core Id
/// </summary>
public int CoreId { get; set; }
/// <summary>
/// Gets or Sets the Language
/// </summary>
public string Language { get; set; }
}
public class ReadCategoryItemFilterDto : PhantomEntityDto
{
public string CategoryItemName { get; set; }
...
public int ItemCount { get; set; }
...
}
Using the Create MultiLingual Map code within the Initialize of the ApplicationModule class:
cfg.CreateMultiLingualMap<CategoryItem, CategoryItemTranslation, ReadCategoryItemFilterDto>(new MultiLingualMapContext(IocManager.Resolve<ISettingManager>()));
The CategoryItemName from the Translation Entity correctly maps to the CategoryItemName on the ReadCategoryItemFilterDto.
Now I need to map the CatalogItems Count to the ItemCount on the Dto.
I have tried adding to the Mapping Configurator:
cfg.CreateMap<CategoryItem, ReadCategoryItemFilterDto>()
.ForMember(dest => dest.ItemCount, opts => opts.MapFrom(src => src.CatalogItems.Count));
and
cfg.CreateMap<CategoryItem, ReadCategoryItemFilterDto>()
.ForMember(dest => dest.ItemCount, opts => opts.ResolveUsing(new CatalogItemFilterCountResolver()));
using
public class CatalogItemFilterCountResolver : IValueResolver<CategoryItem, ReadCategoryItemFilterDto, int>
{
public int Resolve(CategoryItem source, ReadCategoryItemFilterDto destination, int m, ResolutionContext context)
{
return source.CatalogItems.Count;
}
}
If I add this map before the CreateMultiLingualMap the CatalogItemName Maps correctly but the ItemCount fails.
If I put the Projection or Resolver after the CreateMultiLingualMap then the ItemCount maps correctly but the CatalogItemName fails to map.
How do I create a Map Profile to allow for both of the properties to be mapped?
Thank you.
For your case, you don't need to use the extension CreateMultiLingualMap.
You can create your own mapping definition with multilingual mapping included.
That's how it is done;
configuration.CreateMap<CategoryItem, ReadCategoryItemFilterDto>()
.BeforeMap((source, destination, context) =>
{
var translation = source.Translations.FirstOrDefault(pt => pt.Language == CultureInfo.CurrentUICulture.Name);
if (translation != null)
{
context.Mapper.Map(translation, destination);
return;
}
var multiLingualMapContext = new MultiLingualMapContext(IocManager.Instance.Resolve<ISettingManager>());
var defaultLanguage = multiLingualMapContext.SettingManager.GetSettingValue(LocalizationSettingNames.DefaultLanguage);
translation = source.Translations.FirstOrDefault(pt => pt.Language == defaultLanguage);
if (translation != null)
{
context.Mapper.Map(translation, destination);
return;
}
translation = source.Translations.FirstOrDefault();
if (translation != null)
{
context.Mapper.Map(translation, destination);
}
}).ForMember(dest => dest.ItemCount, opts => opts.MapFrom(src => src.CatalogItems.Count));
https://aspnetboilerplate.com/Pages/Documents/Multi-Lingual-Entities#createmultilingualmap
configuration.CreateMultiLingualMap<CategoryItem, CategoryItemTranslation, ReadCategoryItemFilterDto>(context)
.EntityMap.ForMember(dest => dest.ItemCount, opt => opt.MapFrom(src => src.CatalogItems.Count));
There's a distinct smell of burned out circuits coming from my head, so forgive my ignorance.
I'm trying to setup a one-to-one relationship (well, let Automapper do it) in S#arp Architecture.
I have
public class User : Entity
{
public virtual Profile Profile { get; set; }
public virtual Basket Basket { get; set; }
public virtual IList<Order> Orders { get; set; }
public virtual IList<Role> Roles { get; set; }
...
}
public class Basket : Entity
{
public virtual User User { get; set; }
...
}
public class Profile : Entity
{
public virtual User User { get; set; }
...
}
And my db schema is
CREATE TABLE [dbo].[Users](
[Id] [int] IDENTITY(1,1) NOT NULL,
...
CREATE TABLE [dbo].[Profiles](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserFk] [int] NULL,
...
CREATE TABLE [dbo].[Baskets](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserFk] [int] NULL,
...
When I run the unit test CanConfirmDatabaseMatchesMappings in MappingIntegrationTests I get the following error
NHibernate.ADOException : could not
execute query ...
System.Data.SqlClient.SqlException :
Invalid column name 'ProfileFk'.
Invalid column name 'BasketFk'.
and the sql it's trying to execute is
SELECT TOP 0
this_.Id AS Id6_1_ ,
..
user2_.ProfileFk AS ProfileFk9_0_ ,
user2_.BasketFk AS BasketFk9_0_
FROM
Profiles this_
LEFT OUTER JOIN Users user2_
ON this_.UserFk = user2_.Id
So it's looking for a ProfileFk and BasketFk field in the Users table.
I haven't setup any customer override mappings and as far as I can see I've followed the default conventions setup in S#.
The two other mappings for IList Orders and Roles seem to map fine. So I'm guessing that it've missed something for setting up a one-to-one relationship.
What am I missing?
Got it. This is really an NHibernate problem to solve with Fluent NHibernate syntax, but it happens to be relevant for S#.
Background reading: NHibernate Mapping and Fluent NHibernate HasOne
What you do is override the mapping for User and give it two .HasOne mappings. Then set a unique reference to the user on the Profile and Basket class:
public class UserMap : IAutoMappingOverride<User>
{
#region Implementation of IAutoMappingOverride<User>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<User> mapping)
{
mapping.HasOne(u => u.Profile);
mapping.HasOne(u => u.Basket);
}
#endregion
}
public class ProfileMap : IAutoMappingOverride<Profile>
{
#region Implementation of IAutoMappingOverride<Profile>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<Profile> mapping)
{
mapping.References(p => p.User).Unique().Column("UserFk");
}
#endregion
}
public class BasketMap : IAutoMappingOverride<Basket>
{
#region Implementation of IAutoMappingOverride<Basket>
/// <summary>
/// Alter the automapping for this type
/// </summary>
/// <param name="mapping">Automapping</param>
public void Override(AutoMapping<Basket> mapping)
{
mapping.References(b => b.User).Unique().Column("UserFk");
}
#endregion
}
As a side note, at the time of writing this, NHibernate 3 has just been released. There's a great book out called NHibernate 3.0 Cookbook which I've just bought and it looks extremely useful for working with S#.
I have a table http://img36.imageshack.us/i/beztytuuszxq.png/ and mapping:
public class CategoryMap : ClassMap<Category>
{
public CategoryMap()
{
Table(FieldNames.Category.Table);
Id(x => x.ID);
Map(x => x.Name).Not.Nullable();
Map(x => x.ShowInMenuBar).Not.Nullable();
References(x => x.Parent).Column(FieldNames.Category.ID).Nullable();
HasMany(x => x.Articles).Cascade.All().Inverse().Table(FieldNames.Article.Table);
}
}
The entity looks that:
public class Category : EntityBase
{
public virtual int ID { set; get; }
public virtual string Name { set; get; }
public virtual Category Parent { set; get; }
public virtual bool ShowInMenuBar { set; get; }
public virtual IList<Article> Articles { set; get; }
}
When I want to save an Category object to db, when Parent property is set to null, I have exception:
not-null property references a null or transient value CMS.Domain.Entities.Article.Category
I cannot change the
public virtual Category Parent { set; get; }
line to
public virtual Category? Parent { set; get; }
or
public virtual Nullable<Category> Parent { set; get; }
because I have an error during compile:
CMS.Domain.Entities.Category' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'System.Nullable<T>'
I don't know what to change to have possibility to save Category objects without parents.
You can't make a reference type Nullable (as it already is). Nullable<T> (or T?) can only be used with a non-nullable value type (such as int or DateTime).
The error refers to CMS.Domain.Entities.Article.Category - the Category property in the Article class. You haven't provided the map file for the Article entity, however I assume it maps the Category property and either specifies Not.Nullable() or doesn't specific Nullable().
If the domain model allows for the Article entity to contain a null Category use Nullable() otherwise you need to set the category when creating/saving the Article:
Article.Category = aCategory;
The reason you can't Nullable the Category is because Nullable is only meant for value types, and Category, by definition, is a reference type and therefore already can support nulls on properties defined as Category. Can you provide a full stack trace of the exception?
Im guessing you are trying to save an article, (you specified inverse) therefore you need this:
Article.Category = category;
In the CategoriesTranslated collection i have this error: illegal access to loading collection.
public class Category : Entity
{
public Category()
{
CategoriesTranslated = new List<CategoryTranslated>();
}
public virtual Category Parent { get; set; }
public virtual string Name { get; set; }
public virtual IList<CategoryTranslated> CategoriesTranslated { get; set; }
}
public class CategoryTranslated : Entity
{
public CategoryTranslated()
{
}
public virtual Category Category { get; set; }
public virtual LanguageType Language { get; set; }
public virtual string Name { get; set; }
}
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Cascade.All();
}
public void Override(AutoMapping<CategoryTranslated> mapping)
{
mapping.References(x => x.Category);
}
The SQL:
CREATE TABLE Category(
[Id] smallint primary key identity(1,1),
[Parent] smallint null,
[Name] varchar(50) not null unique,
)
alter table [Category] add CONSTRAINT fk_Category_Category
FOREIGN KEY(Parent) references Category (Id)
go
CREATE TABLE CategoryTranslated(
[Id] smallint primary key identity(1,1),
[Category] smallint not null,
[Language] tinyint not null,
[Name] varchar(50) not null,
)
alter table [CategoryTranslated] add CONSTRAINT fk_CategoryTranslated_Category
FOREIGN KEY(Category) references Category (Id)
go
Where is it wrong?
UPDATE
The links to the hbm generater:
Category:
http://uploading.com/files/fmb71565/SubmitSiteDirectory.Core.Category.hbm.xml/
Category translated:
http://uploading.com/files/9c9aaem9/SubmitSiteDirectory.Core.CategoryTranslated.hbm.xml/
I am guessing it has to do with the creation of the list inside the constructor, especially if you left a default ctor for NHib. And that NHib is trying to set the list before it's created. The other complication here is that you have a bi-directional relationship, and CategoryTranslated may be trying to get at the list before its created too.
I doubt this is the only solution, but here is a pattern I use that should solve the error:
/// <summary>Gets the ....</summary>
/// <remarks>This is what is available to outside clients of the domain.</remarks>
public virtual IEnumerable<CategoryTranslated> CategoriesTranslated{ get { return _InternalCategoriesTranslated; } }
/// <summary>Workhorse property that maintains the set of translated categories by:
/// <item>being available to <see cref="Category"/> to maintain data integrity.</item>
/// <item>lazily instantiating the <see cref="List{T}"/> when it is needed.</item>
/// <item>being the property mapped to NHibernate, the private <see cref="_categoriesTranslated"/> field is set here.</item>
/// </list>
/// </summary>
protected internal virtual IList<Category> _InternalCategoriesTranslated
{
get { return _categoriesTranslated?? (_categoriesTranslated= new List<Category>()); }
set { _categoriesTranslated= value; }
}
private IList<StaffMember> _categoriesTranslated;
Now you need to set your mapping to access the private field, so assuming you use my casing preferences here, you'd have:
public void Override(AutoMapping<Category> mapping)
{
mapping.HasMany(x => x.CategoriesTranslated)
.Inverse()
.Access.CamelCaseField(CamelCasePrefix.Underscore)
.Cascade.All();
}
HTH,
Berryl
EDIT ============
The _Internal collection also gives the child of of the bi-directional relationship, CategoryTranslated in this case, a hook, as shown in the code below:
public virtual CategoryTranslated CategoryTranslated
{
get { return _categoryTranslated; }
set
{
if (_categoryTranslated!= null)
{
// disassociate existing relationship
_categoryTranslated._InternalCategoryTranslated.Remove(this);
}
_categoryTranslated= value;
if (value != null)
{
//make the association
_categoryTranslated._InternalCategoryTranslated.Add(this);
}
}
}
private CategoryTranslated _categoryTranslated;
Basic question: How to I create a bidirectional one-to-many map in Fluent NHibernate?
Details:
I have a parent object with many children. In my case, it is meaningless for the child to not have a parent, so in the database, I would like the foreign key to the parent to have NOT NULL constraint. I am auto-generating my database from the Fluent NHibernate mapping.
I have a parent with many child objects like so:
public class Summary
{
public int id {get; protected set;}
public IList<Detail> Details {get; protected set;}
}
public class Detail
{
public int id {get; protected set;}
public string ItemName {get; set;}
/* public Summary Owner {get; protected set;} */ //I think this might be needed for bidirectional mapping?
}
Here is the mapping I started with:
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.ID);
HasMany<Detail>(x => x.Details);
}
}
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.ID);
Map(x => x.ItemName).CanNotBeNull();
}
}
In the Detail table, the Summary_id should be Not Null, because in my
case it is meaningless to have a Detail object not attached to the
summary object. However, just using the HasMany() map leaves the Summary_id foreign key nullable.
I found in the NHibernate docs (http://www.hibernate.org/hib_docs/nhibernate/html/collections.html) that "If the parent is required, use a bidirectional one-to-many association".
So how do I create the bidirectional one-to-many map in Fluent NHibernate?
To get a bidirectional association with a not-null foreign key column in the Details table you can add the suggested Owner property, a References(...).CanNotBeNull() mapping in the DetailsMap class, and make the Summary end inverse.
To avoid having two different foreign key columns for the two association directions, you can either specify the column names manually or name the properties in a way that gives the same column name for both directions. In this case you I suggest renaming the Details.Owner property to Details.Summary.
I made the Summary id generated by increment to avoid problems when inserting into the table since Summary currenty has no columns besides id.
Domain:
public class Detail
{
public int id { get; protected set; }
public string ItemName { get; set; }
// Renamed to use same column name as specified in the mapping of Summary.Details
public Summary Summary {get; set;}
}
public class Summary
{
public Summary()
{
Details = new List<Detail>();
}
public int id { get; protected set; }
public IList<Detail> Details { get; protected set; }
}
Mapping:
public class DetailMap : ClassMap<Detail>
{
public DetailMap()
{
Id(x => x.id)
.GeneratedBy.Native();
Map(x => x.ItemName)
.CanNotBeNull();
References<Summary>(x => x.Summary)
// If you don't want to rename the property in Summary,
// you can do this instead:
// .TheColumnNameIs("Summary_id")
.CanNotBeNull();
}
}
public class SummaryMap : ClassMap<Summary>
{
public SummaryMap()
{
Id(x => x.id)
.GeneratedBy.Increment();
HasMany<Detail>(x => x.Details)
.IsInverse()
.AsBag(); // Use bag instead of list to avoid index updating issues
}
}