Fluent NHibernate child class mapping using DiscriminateSubClassesOnColumn issue - c#

I have an issue with mapping, simplified my relationship looks like this.
I have parent class:
public abstract class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
and two subclasses:
public class UploadedFileDocument : DocumentType
{
}
public class ApplicationFormDocument : DocumentType
{
}
mapped like this:
public DocumentTypeMap()
{
Schema("Core");
Id(x => x.Id);
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
public class UploadedFileDocumentMap : SubclassMap<UploadedFileDocument>
{
}
public class ApplicationFormDocumentMap : SubclassMap<ApplicationFormDocument>
{
}
Then I have another entity with a FK to DocumentType, mapped like this:
public FileConversionMap()
{
Schema("Core");
Id(x => x.Id);
References(x => x.Application).Not.Nullable();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
my issue is, when I retrieve rows from the DB like this:
Session.Query<FileConversion>().AsQueryable();
all the rows come back with the DocumentType being of type DocumentType, not of the child type (ie the actual type of that property, ie. when i do .GetType(), either UploadedFileDocument or ApplicationFormDocument)
Apologies if this is just me being dim. But how can I determine which type of DocumentType I have ... is my mapping wrong?

When you look at your generated SQL (adding .ShowSQL() to your .Database method), do you see the Type being entered? You should see something similar to:
INSERT
INTO
"Core_DocumentType"
(Name, Type)
VALUES
(#p0, 'ApplicationFormDocument');
select
last_insert_rowid();
#p0 = 'afd' [Type: String (0)]
Using the mappings you provided, it looks fine and I could return the DocumentType (using SQLite) just fine.
Here's the code I used to reproduce it. I didn't have your FileConversion object, so please verify that it matches what you need.
DocumentType
public class DocumentType
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
public class DocumentTypeMap : ClassMap<DocumentType>
{
public DocumentTypeMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Length(128).Not.Nullable();
DiscriminateSubClassesOnColumn("Type");
}
}
UploadFileDocument/ApplicationFormDocument
public class UploadedFileDocument : DocumentType
{
public virtual string ContentType { get; set; }
}
public class ApplicationFormDocument : DocumentType
{
}
public class UploadFileDocumentMap :
SubclassMap<UploadedFileDocument>
{
public UploadFileDocumentMap()
{
GenerateMap();
}
void GenerateMap()
{
Map(x => x.ContentType);
}
}
public class ApplicationFormDocumentMap :
SubclassMap<ApplicationFormDocument>
{
}
FileConversion
public class FileConversion
{
public virtual int Id { get; set; }
public virtual DocumentType DocumentType { get; set; }
}
public class FileConversionMap : ClassMap<FileConversion>
{
public FileConversionMap()
{
GenerateMap();
}
void GenerateMap()
{
Schema("Core");
Id(x => x.Id).GeneratedBy.Identity();
References(x => x.DocumentType).Not.Nullable().Fetch.Select();
}
}
The tests I used (using machine.specifications):
Context
public class when_discriminating_on_subclass
{
static IList<FileConversion> results;
Establish context = () =>
{
using (var session = DataConfiguration.CreateSession())
{
using (var transaction = session.BeginTransaction())
{
var upload = new UploadedFileDocument
{ Name = "uploaded", ContentType = "test" };
var form = new ApplicationFormDocument
{ Name = "afd" };
session.Save(form);
session.Save(upload);
var formConversion =
new FileConversion { DocumentType = form };
var uploadConversion =
new FileConversion { DocumentType = upload };
session.Save(formConversion);
session.Save(uploadConversion);
transaction.Commit();
}
using (var transaction = session.BeginTransaction())
{
results = session.Query<FileConversion>().AsQueryable().ToList();
transaction.Commit();
}
}
};
Specifications
It should_return_two_results = () =>
results.Count.ShouldEqual(2);
It should_contain_one_of_type_uploaded_file = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(UploadedFileDocument))
.ShouldEqual(1);
It should_contain_one_of_type_application_form = () =>
results
.Count(x => x.DocumentType.GetType() == typeof(ApplicationFormDocument))
.ShouldEqual(1);
}
Debugging through the assertions, I can see that the collection comes back with the two types:
Are you casting them back to the base type anywhere in your mappings or classes?

Related

Automapper suddenly creates nested object

Entities:
public class Entity
{
public int Id { get; set; }
}
public class User : Entity
{
public string Name { get; set; }
public Company Company { get; set; }
}
public class Company : Entity
{
public string Name { get; set; }
}
Dto's:
public class EntityDto
{
public int Id { get; set; }
}
public class UserDto : EntityDto
{
public string Name { get; set; }
public int? CompanyId { get; set; }
}
So I want to map User to UserDto like User.Company == null => UserDto.CompanyId == null and vice versa.
That is my Automapper configuration:
Mapper.Initialize(configuration =>
{
configuration
.CreateMap<User, UserDto>()
.ReverseMap();
});
This works fine:
[Fact]
public void UnattachedUserMapTest()
{
// Arrange
var user = new User { Company = null };
// Act
var userDto = Mapper.Map<User, UserDto>(user);
// Assert
userDto.CompanyId.Should().BeNull();
}
but this test fails:
[Fact]
public void UnattachedUserDtoMapTest()
{
// Arrange
var userDto = new UserDto { CompanyId = null };
// Act
var user = Mapper.Map<UserDto, User>(userDto);
// Assert
user.Company.Should().BeNull();
}
Details:
Expected object to be <null>, but found
Company
{
Id = 0
Name = <null>
}
Doesn't work for me:
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Condition(dto => dto.CompanyId != null));
and well as that (just for example):
...
.ReverseMap()
.ForMember(user => user.Company, opt => opt.Ignore());
Why does Automapper create nested object and how can I prevent it?
That "suddenly" bit is funny :)
configuration.CreateMap<User, UserDto>().ReverseMap().ForPath(c=>c.Company.Id, o=>o.Ignore());
You have a default MapFrom with CompanyId and that is applied in reverse. For details see this and a few other similar issues.
In the next version (on MyGet at the moment) you'll also be able to use
configuration.CreateMap<User, UserDto>().ReverseMap().ForMember(c=>c.Company, o=>o.Ignore());

Fluent NHibernate: ISet of base class

In my project I have a base class (not mapped):
public abstract class BaseEntity
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
Also I have a few inherited classes (they look all almost the same, so here is a code and map for only one)
public class User : BaseEntity
{
public virtual int UserId { get; set; }
public virtual string Login { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Entities { get; set; }
}
public class UserMap : ClassMap<User>
{
public UserMap()
{
this.Id(x => x.UserId);
this.Map(x => x.Login);
this.Map(x => x.PasswordHash);
this.HasManyToMany<BaseEntity>(x => x.Entities);
}
}
Next, I have a NHibernateHelper:
public class NHibernateHelper
{
public static ISession OpenSession()
{
ISessionFactory sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(#"someconstring")
.ShowSql()
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<User>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.BuildSessionFactory();
return sessionFactory.OpenSession();
}
}
And here is a question:
How can I exclude BaseEntity class from mapping, if I need table like EnitiyToEntity in my Database for many-to-many relationship?
Take a look to this:
https://www.codeproject.com/Articles/232034/Inheritance-mapping-strategies-in-Fluent-Nhibernat
If I understand your question the solution should be to implement TPC (Table per concrete class).
By the way, in your mapping you have to use the concrete type for HasManyToMany.
For example (I supposed your user is referenced to many groups):
HasManyToMany<Group>(x => x.Entities).Table("UsersGroups");
where the Group class is something like this:
public class Group : BaseEntity
{
public virtual int GroupId { get; set; }
public virtual string PasswordHash { get; set; }
public virtual ISet<BaseEntity> Members { get; set; }
}
And in the GroupMap class you can reference the users like this:
HasManyToMany<User>(x => x.Members).Table("UsersGroups");
If you reference a class you have to map it. So map Entity as ClassMap and all the others as SubclassMap. They will end up as union subclass which is one table per class. Unfortunatly you can not map a hasmanytoany with FNH.
You can map it as hasmanytomany and work around it:
var config = new Configuration();
config.BeforeBindMapping += BeforeBindMapping;
_config = Fluently
.Configure(config)
...
private void BeforeBindMapping(object sender, NHCfg.BindMappingEventArgs e)
{
var userclass = e.Mapping.RootClasses.FirstOrDefault(rc => rc.name.StartsWith(typeof(User).FullName));
if (userclass != null)
{
HbmSet prop = (HbmSet)paymentclass.Properties.FirstOrDefault(rc => rc.Name == "Entities");
prop.Item = new HbmManyToAny // == prop.ElementRelationship
{
column = new[]
{
new HbmColumn { name = "entityType", notnull = true, notnullSpecified = true },
new HbmColumn { name = "entity_id", notnull = true, notnullSpecified = true }
},
idtype = "Int64",
metatype = "String",
metavalue = typeof(Entity).Assembly.GetTypes()
.Where(t => !t.IsInterface && !t.IsAbstract && typeof(Entity).IsAssignableFrom(t))
.Select(t => new HbmMetaValue { #class = t.AssemblyQualifiedName, value = t.Name })
.ToArray()
};
}
}

NHibernate Envers: Cannot insert duplicate key in object

I'm using Envers to audit tables, but it's creating some audit tables for unknown/absent tables.
It's looks like a Many To Many relation audit table for Many To One relations.
Is this right? If it's, Why?
dbo.HorarioFixo - OK
dbo.HorarioFixo_Auditoria - OK
dbo.HorarioFixo_JanelaHorarioFixo_Auditoria - NOK
dbo.JanelaHorarioFixo - OK
dbo.JanelaHorarioFixo_Auditoria - OK
But when I try to remove/delete and HorarioFixo I'm getting an error.
The error I'm getting:
NHibernate.Exceptions.GenericADOException
could not execute batch command.[SQL: SQL not available]
em NHibernate.Engine.ActionQueue.BeforeTransactionCompletionProcessQueue.BeforeTransactionCompletion()
em NHibernate.Impl.SessionImpl.BeforeTransactionCompletion(ITransaction tx)
em NHibernate.Transaction.AdoTransaction.Commit()
em Foo.Testes.Servicos.TesteCanalDeTransmissaoService.RemoveDependenciasCorretamente() na TesteCanalDeTransmissaoService.cs: line 195
System.Data.SqlClient.SqlException
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
Violation of PRIMARY KEY constraint 'PK__HorarioF__450088476960C81E'. Cannot insert duplicate key in object 'dbo.HorarioFixo_JanelaHorarioFixo_Auditoria'.
The statement has been terminated.
The statement has been terminated.
This is the SQL duplicated:
exec sp_executesql N'INSERT INTO HorarioFixo_JanelaHorarioFixo_Auditoria (REVTYPE, REV, HorarioFixoId, JanelaHorarioFixoId) VALUES (#p0, #p1, #p2, #p3)',N'#p0 tinyint,#p1 int,#p2 bigint,#p3 bigint',#p0=2,#p1=3,#p2=1,#p3=2 go
All this is a part of the code. If you need something more, leave a comment.
My classes:
public class Entidade
{
protected Entidade();
public virtual long Id { get; set; }
public virtual long Version { get; set; }
public abstract override bool Equals(object obj);
public override int GetHashCode();
}
public class Horario : Entidade
{
protected Horario()
{
}
}
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRemessa { get; set; }
public virtual ICollection<JanelaHorarioFixo> JanelasRetorno { get; set; }
}
public class JanelaHorarioFixo : Entidade
{
public virtual TimeSpan HorarioInicio { get; set; }
public virtual TimeSpan? HorarioLimite { get; set; }
}
My mappings:
public class HorarioMap : ClassMapping<Horario>
{
public HorarioMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
}
}
public class HorarioFixoMap : JoinedSubclassMapping<HorarioFixo>
{
public HorarioFixoMap()
{
Property(x => x.Frequencia);
Bag(x => x.JanelasRemessa, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
Bag(x => x.JanelasRetorno, m =>
{
m.Cascade(Cascade.All);
m.Lazy(CollectionLazy.NoLazy);
}, map => map.OneToMany());
}
}
public class JanelaHorarioFixoMap : ClassMapping<JanelaHorarioFixo>
{
public JanelaHorarioFixoMap()
{
Id(x => x.Id, mapper =>
{
mapper.Generator(Generators.Identity);
mapper.UnsavedValue(0);
});
Property(x => x.HorarioInicio, m => m.NotNullable(true));
Property(x => x.HorarioLimite, m => m.NotNullable(false));
}
}
NH and Envers configurations:
var ormHelper = ORMHelperUtils.GetORMHelper();
var mapper = new MyConventionModelMapper();
_config = new Configuration();
mapper.AddMappings(ormHelper.GetMappings());
mapper.AddMapping(typeof(REVINFOMap));
ormHelper.SetupApplicationNeeds(_config);
_config.AddMapping(mapper.CompileMappingForAllExplicitlyAddedEntities());
_config.SetProperty(Environment.CurrentSessionContextClass, "call");
if (ormHelper.UseEnvers)
{
var classesDominio = ormHelper.GetDomainTables();
if (classesDominio.Any())
{
var envers = new FluentConfiguration();
envers.Audit(classesDominio);
envers.SetRevisionEntity<REVINFO>(e => e.Id, e => e.Date, new CustomRevisionListener());
_config.SetEnversProperty(ConfigurationKey.AuditTableSuffix, "_Auditoria");
_config.IntegrateWithEnvers(envers);
}
}
I've just changed my class to
public class HorarioFixo : Horario
{
public virtual int Frequencia { get; set; }
public virtual ICollection<JanelaHorarioFixo> Janelas { get; set; }
}
And added a property to JanelaHorarioFixo to identify the type. But the table dbo.HorarioFixo_JanelaHorarioFixo_Auditoria is still there, and I don't know why.
If you use unidirectional one-to-many, Envers needs a link table to be able to have correct history.
If you use bidirectional one-to-many, no link table is needed.
See this answer.

NHibernate: Save Object1 with List<Object2> into database

I have problem with my Nhibernate app.
I have class called Rozmiar, and class called Symbol.
Symbol contains List as property.
How can I save it into database using NHibernate?
My code (doesn't working properly):
SYMBOL CLASS:
public class Symbol
{
public virtual int Id { get; set; }
public virtual string Nazwa { get; set; }
public virtual bool Sitodruk { get; set; }
public virtual List<Rozmiar> Rozmiar { get; set; }
public Symbol() { }
public Symbol(string nazwa, List<Rozmiar> lista)
{
using (ISession sesja = Program.baza.SessionFactory.OpenSession())
{
using (ITransaction transaction = sesja.BeginTransaction())
{
Symbol s = new Symbol();
s.Nazwa = nazwa;
s.Rozmiar = lista;
sesja.Save(s);
transaction.Commit();
}
}
}
}
ROZMIAR CLASS:
public class Rozmiar
{
public virtual int Id { get; set; }
public virtual string Nazwa { get; set; }
public Rozmiar() { }
public Rozmiar(string nazwa)
{
using (ISession sesja = Program.baza.SessionFactory.OpenSession())
{
using (ITransaction transaction = sesja.BeginTransaction())
{
Rozmiar r = new Rozmiar();
r.Nazwa = nazwa;
sesja.Save(r);
transaction.Commit();
}
}
}
}
MAP:
public class RozmiarMap : ClassMap<Rozmiar>
{
public RozmiarMap()
{
Table("Rozmiar");
SchemaAction.All();
Id(x => x.Id).GeneratedBy.Identity();
Map(X => X.Nazwa).Not.Nullable();
}
}
public class SymbolMap : ClassMap<Symbol>
{
public SymbolMap()
{
Table("Symbol");
SchemaAction.All();
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Nazwa).Not.Nullable();
HasMany(x => x.Rozmiar).KeyColumn("IdRozmiar");
}
}
And method to generate sample objects:
public static void Generuj()
{
List<Rozmiar> listA = new List<Rozmiar>();
listA.Add(new Rozmiar("750 mm"));
listA.Add(new Rozmiar("900 mm"));
listA.Add(new Rozmiar("1050 mm"));
listA.Add(new Rozmiar("1200 mm"));
Symbol a1 = new Symbol("A-1", listA);
Symbol a2 = new Symbol("A-2", listA);
Symbol a3 = new Symbol("A-3", listA);
}
In debugging mode, I saw, that listA contains 4 objects, but properties of those objects ale Id = 0 and Nazwa = null :( Maybe there is a main problem...
I also don't know, if my mapping is set properly.
BTW. Connections settings and config are ok, because i have other classes in this code, and it saves properly into database.
In the constructor of Rozmiar with string parameter, you are creating different (new) instance of the Rozmiar. So while you are adding into the listA instance created by
new Rozmiar("750 mm")
persisted is different object
Rozmiar r = new Rozmiar();
Also, change your mapping from List<Rozmiar> into IList<Rozmiar>. See here: 6.1. Persistent Collections the complete list of supported interfaces for collection mapping

Fluent NHibernate mapping a dictionary with a key that is a component

I've created the following two classes with their respective mappings:
public class ParentClass
{
public int Id { get; set; }
public IDictionary<YearMonth, ChildClass> SomeMap { get; set; }
}
public class ParentClassMap : ClassMap<ParentClass>
{
public ParentClassMap()
{
Table("ParentClass");
Id(x => x.Id);
HasMany(x => x.SomeMap)
.Table("ChildClass")
.KeyColumn("ParentId")
.AsMap(x => x.YearMonth);
}
}
public class ChildClass
{
public int Id { get; set; }
public YearMonth YearMonth { get; set; }
}
public class ChildClaassMap : ClassMap<ChildClass>
{
public ChildClaassMap()
{
Table("ChildClass");
Id(x => x.Id);
Component(x => x.YearMonth, x =>
{
x.Map(Reveal.Member<YearMonth>("_dateTime"))
.Access.Field()
.Column("`Date`");
});
}
}
When testing the mapping for the ParentClass, though, the following error is thrown:
[TestFixture]
public class ParentClassMapTests : IntegrationTestBase
{
[Test]
public void Should_be_retrievable()
{
new PersistenceSpecification<ParentClass>(GetSession(), new PersistentObjectEqualityComparer())
.CheckProperty(Reveal.Member<ParentClass>("_dataDictionary"), new Dictionary<YearMonth, BenchmarkPieceData> { { new YearMonth(2010, 1), new BenchmarkPieceData(null, new YearMonth(2010, 1), 0) } })
.VerifyTheMappings();
}
}
NHibernate.MappingException : Could not determine type for: BenchmarkPlus.CommonDomain.ValueObjects.YearMonth, BenchmarkPlus.CommonDomain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns: NHibernate.Mapping.Column(YearMonth)
at NHibernate.Mapping.SimpleValue.get_Type()
at NHibernate.Mapping.SimpleValue.IsValid(IMapping mapping)
at NHibernate.Mapping.IndexedCollection.Validate(IMapping mapping)
at NHibernate.Cfg.Configuration.ValidateCollections()
at NHibernate.Cfg.Configuration.Validate()
at NHibernate.Cfg.Configuration.BuildSessionFactory()
NHibernate\SessionFactoryBuilder.cs(24,0): at BenchmarkPlus.RMS.Infrastructure.NHibernate.SessionFactoryBuilder.BuildFactory()
NHibernate\SessionFactoryBuilder.cs(13,0): at BenchmarkPlus.RMS.Infrastructure.NHibernate.SessionFactoryBuilder..ctor(IConfigurationFactory configurationFactory)
If the type is changed from a YearMonth to a DateTime, the mapping works fine. Am I suppose to be able to map to an IDictionary with my YearMonth type as the key? If so, what am I doing wrong?

Categories