I've used Fluent NH in some projects but I'm having some problems with using the PersistenceSpecification class for testing a collection mapping. Here's the code for my classes (I'm just putting here the collection definition):
public class Ocorrencia : EntityWithAction, IHasAssignedId<Int32> {
private IList<Intervencao> _intervencoes = new List<Intervencao>();
public IEnumerable<Intervencao> Intervencoes {
get{
return new ReadOnlyCollection<Intervencao>( _intervencoes );
}
set {
_intervencoes = new List<Intervencao>( value );
Contract.Assume(_intervencoes != null);
}
}
public void ModificaEstado(Intervencao intervencao ){
//some checks removed
intervencao.Ocorrencia = this;
_intervencoes.Add(intervencao);
}
//more code removed
}
public class Intervencao : EntityWithAction, IHasAssignedDomainAction {
//other code remove
internal Ocorrencia Ocorrencia { get; set; }
}
And here's the mappings (only the important things):
public class IntervencaoMapping: ClassMap<Intervencao> {
public IntervencaoMapping()
{
WithTable("Intervencoes");
Not.LazyLoad();
Id(intervencao => intervencao.Id)
.ColumnName("IdIntervencoes")
.WithUnsavedValue(0)
.SetGeneratorClass("identity");
Map(intervencao => intervencao.Guid, "Guid")
.Not.Nullable();
Version(ent => ent.Version)
.ColumnName("Version");
References(ent => ent.Action, "IdAccao")
.Cascade
.SaveUpdate();
Map(intervencao => intervencao.TipoEstado, "TipoEstado")
.CustomTypeIs(typeof (TipoEstado))
.CustomSqlTypeIs("integer");
Map(intervencao => intervencao.Observacoes, "Observacoes");
References(intervencao => intervencao.Ocorrencia, "IdOcorrencias")
.Not.LazyLoad();
}
}
public class OcorrenciaMapping: ClassMap<Sra.Ocorrencias.Ocorrencia> {
public OcorrenciaMapping()
{
WithTable("Ocorrencias");
Not.LazyLoad();
Id(ocorrencia => ocorrencia.Id)
.ColumnName("IdOcorrencias")
.WithUnsavedValue(0)
.SetGeneratorClass("identity");
Map(ocorrencia => ocorrencia.Guid, "Guid")
.Not.Nullable();
Version(ocorrencia => ocorrencia.Version)
.ColumnName("Version");
Map(ocorrencia => ocorrencia.Descricao)
.ColumnName("Descricao");
Map(ocorrencia => ocorrencia.Nif, "Nif")
.Not.Nullable();
Map(ocorrencia => ocorrencia.TipoOcorrencia, "TipoOcorrencia")
.CustomTypeIs(typeof(TipoOcorrencia))
.CustomSqlTypeIs("integer");
Map(ocorrencia => ocorrencia.BalcaoEntrada, "Balcao")
.CustomTypeIs(typeof(TipoBalcao))
.CustomSqlTypeIs("integer")
.Not.Nullable();
References(ocorrencia => ocorrencia.Organismo, "IdOrganismos")
.Cascade.None()
.Not.Nullable();
HasMany(ocorrencia => ocorrencia.Intervencoes)
.Access.AsCamelCaseField(Prefix.Underscore)
.AsBag()
.Cascade
.All()
.KeyColumnNames.Add("IdOcorrencias")
.Not.LazyLoad();
}
}
As you can see, Interncao objects are added through the ModificaEstado method which ensures that Ocorrencia reference on Intervencao "points" to a reference of Ocorrencia. Now, how do I test this relationship with the PersistenceSpecification object? I've ended up with the following code:
[Test]
public void Test() {
using (var session = _factory.GetSession()) {
using (var tran = session.BeginTransaction()) {
var accao = CreateAction();
session.Save(accao);
var organismo = CreateOrganismo();
session.Save(organismo);
var intervencao = CreateIntervencao();
((IHasAssignedDomainAction)intervencao).SetActionTo(accao);
var intervencoes = new List<Intervencao> {intervencao};
new PersistenceSpecification<Ocorrencia>(session)
.CheckProperty(e => e.Nif, _nif)
.CheckProperty( e =>e.Organismo, organismo)
.CheckProperty( e => e.Descricao, _descricao)
.CheckProperty( e => e.TipoOcorrencia, TipoOcorrencia.Processo)
.CheckList( e => e.Intervencoes, intervencoes)
.VerifyTheMappings());
tran.Rollback();
}
}
}
Since IdOcorrencia is defined as an external key in table Intervencoes, the previous code fails because it tries to insert the intervencoes list with IdOcorrencia set to null. If I remove the external key, then the test works fine, but I believe that I shouldn't be doing that.
I'm probably doing something wrong but I'm not sure on what that is. So, can anyone be kind enough and give me a hint on how to solve this?
thanks guys.
Luis
The problem was that I was using an old version of the fluent nhibernate. recente versions have overrides which let you solve this kind of problem:
http://www.mail-archive.com/fluent-nhibernate#googlegroups.com/msg06121.html
Related
I am querying a list of objects, and then correlating them with a subquery.
I want to return the results of the subquery, as well as the root entity. But
I can't figure out how to actually return the root entity, I can only return individual properties of it.
Specifically, this works:
this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list.Select(mf => mf.Id));
But this does not:
this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list.Select(mf => mf));
I get the following error:
Could not resolve property : of MediaFile
Does anyone know how I can include the entity as a property in the list?
Here's my complete example:
// My target class
private class MediaFileAndCount
{
// I can successfully populate these fields.
public long MediaFileId { get; set; }
public int DistinctPlaylistCount { get;set; }
// But I want to populate this field!
public MediaFile MediaFile { get; set; }
}
private void TrySingleQuery()
{
MediaFile mediaFile = null;
PlaylistEntry playlistEntry = null;
MediaFileAndCount mfc = null;
var subQuery = QueryOver.Of<PlaylistEntry>(() => playlistEntry)
.Where(() => playlistEntry.MediaFile.Id == mediaFile.Id)
.Select(Projections.CountDistinct<PlaylistEntry>(p => p.Playlist));
var query = this.Session.QueryOver<MediaFile>(() => mediaFile)
.SelectList(list => list
.Select(mf => mf.Id).WithAlias(() => mfc.MediaFileId)
.Select(Projections.SubQuery(subQuery)).WithAlias(() => mfc.DistinctPlaylistCount)
// .Select(mf => mf).WithAlias(() => mfc.MediaFile) // This line fails
)
.TransformUsing(Transformers.AliasToBean<MediaFileAndCount>());
var results = query.List<MediaFileAndCount>();
}
another way to query it
var allMediaFiles = session.QueryOver<MediaFile>().Where(...).ToFuture(); // just get the MediaFiles into sessionCache
var results = session.QueryOver<PlaylistEntry>()
.Where(p => p.MediaFile...)
.SelectList(list => list
.GroupBy(p => p.MediaFile.Id)
.CountDistinct(p => p.Playlist))
.ToFuture<object[]>()
.Select(a => new MediaFileAndCount
{
MediaFile = session.Get<MediaFile>((long)a[0]),
DistinctPlaylistCount = (int)a[1]
})
.ToList();
foreach (var mediaFile in allMediaFiles.Except(results.Select(r => r.MediaFile)))
{
results.Add(new MediaFileAndCount { MediaFile = mediaFile });
}
I use this in EF
var users = Context.CreateSet<User>()
.Select(u => new {
User = u,
Salary = u.Salaries.Where(s => !s.Deleted)
})
.AsEnumerable()
.Select(a => a.User);
There is a Linq way to do this in NHibernate and i want it work for navigation property load by lazy load too
NHibernate will not fill the collection with filtered results this way and it is not a good idea because you get a broken model. The best options i see
var users = session.Query<User>()
.Fetch(u => u.Salaries)
.Select(u => new
{
User = u,
ActiveSalaries = u.Salaries.Where(s => !s.Deleted)
});
or
public virtual IEnumerable<Salary> ActiveSalaries
{
get { return Salaries.Where(s => !s.Deleted); }
}
var users = session.Query<User>()
.Fetch(u => u.Salaries)
or
// ctor
ActiveSalaries = Salaries.Where(s => !s.Deleted);
// property
public virtual IEnumerable<Salary> ActiveSalaries { get; private set; }
// mapping almost same as that of Salaries
HasMany(x => x.ActiveSalaries).Table("Salaries").Where("Deleted=0");
var users = session.Query<User>()
.Fetch(u => u.ActiveSalaries);
I have defined this mapping:
public class Mapping : ConventionModelMapper
{
public Mapping()
{
IsRootEntity((type, declared) =>
{
return !type.IsAbstract &&
new[] { typeof(Entity<Guid>), typeof(CommonEntity) }.Contains(type.BaseType);
});
IsEntity((x, y) => typeof(Entity<Guid>).IsAssignableFrom(x) && !x.IsAbstract && !x.IsInterface);
IsSet((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
return propertyType.IsGenericType && typeof(System.Collections.Generic.ISet<>).IsAssignableFrom(propertyType.GetGenericTypeDefinition());
});
IsManyToMany((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
var containingType = mi.ReflectedType;
if (typeof(System.Collections.Generic.ISet<>).IsAssignableFrom(propertyType.GetGenericTypeDefinition()))
{
var referenceType = propertyType.GetGenericArguments()[0];
return true;
return !referenceType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Any(p => p.PropertyType.IsAssignableFrom(containingType));
}
return false;
});
Class<Entity<Guid>>(x =>
{
x.Id(c => c.Id, m => m.Generator(Generators.GuidComb));
x.Version(c => c.Version, (vm) => { });
});
BeforeMapClass += OnBeforeMapClass;
BeforeMapManyToOne += OnBeforeMapManyToOne;
BeforeMapSet += OnBeforeMapSet;
BeforeMapManyToMany += OnBeforeMapManyToMany;
Class<CommonEntity>(x =>
{
x.Property(c => c.DateCreated, m => m.Type<UtcDateTimeType>());
x.Property(c => c.DateModified, m => m.Type<UtcDateTimeType>());
});
}
private void OnBeforeMapManyToMany(IModelInspector modelInspector, PropertyPath member, IManyToManyMapper collectionRelationManyToManyCustomizer)
{
collectionRelationManyToManyCustomizer.Column(member.LocalMember.GetPropertyOrFieldType().GetGenericArguments()[0].Name + "Id");
}
private void OnBeforeMapSet(IModelInspector modelInspector, PropertyPath member, ISetPropertiesMapper propertyCustomizer)
{
propertyCustomizer.Key(k=>k.Column(member.GetContainerEntity(modelInspector).Name + "Id"));
propertyCustomizer.Cascade(Cascade.Persist);
if (modelInspector.IsManyToMany(member.LocalMember))
{
propertyCustomizer.Table(member.GetContainerEntity(modelInspector).Name +
member.LocalMember.GetPropertyOrFieldType().GetGenericArguments()[0].Name);
}
}
private void OnBeforeMapManyToOne(IModelInspector modelInspector, PropertyPath member, IManyToOneMapper propertyCustomizer)
{
propertyCustomizer.Column(member.LocalMember.Name + "Id");
}
private void OnBeforeMapClass(IModelInspector modelInspector, Type type, IClassAttributesMapper classCustomizer)
{
classCustomizer.Table('['+ type.Name + ']');
}
}
An I am having a problem with a many to many relationship. I have User, UserPermission and Permission. When I am saving a user after attaching a Permission to it it generates this SQL:
exec sp_executesql N'UPDATE [Permission] SET UserId = #p0 WHERE Id = #p1',N'#p0 uniqueidentifier,#p1 uniqueidentifier',#p0='57A2CD87-4A79-4131-B9CE-A1060168D520',#p1='9D99D340-1B63-4291-B55A-6127A8F34FC9'
When it should be like:
exec sp_executesql N'INSERT INTO UserPermission (UserId, PermissionId) VALUES (#p0, #p1)',N'#p0 uniqueidentifier,#p1 uniqueidentifier',#p0='2C670A01-C2E6-46A3-A412-A1060168F976',#p1='9D99D340-1B63-4291-B55A-6127A8F34FC9'
When I add a specific class mapping for User:
Class<User>(classMapper =>
{
classMapper.Set(x => x.Permissions, map =>
{
//map.Key(k => k.Column("UserId"));
//map.Table("UserPermission");
}, r => r.ManyToMany(m => {}));
});
I can leave out the key and table definition and include the ManyToMany without the column call and it works. But it does the same same thing as my BeforeManyToMany event handler. If I drop the whole Class thing the BeforeMapManyToMany event is not fired and nHibernate thinks I've got a UserId on my Permission table.
Heres User:
public class User : CommonEntity
{
protected User()
{
Permissions = new HashSet<Permission>();
}
public User(User createdBy) : base(createdBy)
{
Permissions = new HashSet<Permission>();
}
public ISet<Permission> Permissions { get; protected set; }
}
After poking around in the source code I realised the problem was that IsOneToMany was checked against the property when defining the set before IsManyToMany. I just needed to define IsOneToMany and it worked without any explicit mappings.
IsOneToMany((mi, wasDeclared) =>
{
var propertyType = mi.GetPropertyOrFieldType();
return ModelInspector.IsEntity(propertyType);
});
above is code which I use to manipulate with data from my domain to dto model, which I use for wcf serialization. My question is how to pass object mother with collection of childrens into MotherDTO. With current code situation I pass only data without collection children. Do I need to use session in line and to add session MotherDTO dto = new MotherDTO(data, session); and to use that session to retreive collection of childrens in dto. If so, how ? Please help.
Regards,
public MotherDTO GetMotherData()
{
using (ISession session = instance.OpenSession())
{
using (ITransaction tx = session.BeginTransaction())
{
Mother data = session.Query<Mother>()
.Fetch(x => x.Childrens)
.FirstOrDefault();
tx.Commit();
MotherDTO dto = new MotherDTO(data);
return dto;
}
}
}
MotherDTO.cs
public MotherDTO(Mother x)
{
Name = x.Name;
List<Children>Childrens= new List<Children>();
foreach (Children obj in x.Childrens)
{
States.Add(obj);
}
}
Mother.cs
public virtual string Name
{
get { return _Name; }
set
{
_Name = value;
}
}
public virtual Iesi.Collections.Generic.ISet<Children> Childrens
{
get
{
return _Childrens;
}
set
{
if (_Childrens == value)
return;
_Childrens = value;
}
}
Since you're already (eager) loading your Children collection you can use Automapper to populate your DTOs.
If you want to know how to configure Automapper to work with nested collection you can read here:
Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.OrderLineDtos, opt => opt.MapFrom(src => src.OrderLines));
Mapper.CreateMap<OrderLine, OrderLineDto>()
.ForMember(dest => dest.ParentOrderDto, opt => opt.MapFrom(src => src.ParentOrder));
Mapper.AssertConfigurationIsValid();
I'm trying to do this:
Key key = session.QueryOver<Key>()
.Left.JoinQueryOver<ConfigValue>(x => x.ConfigValues)
.Where(c => c.Id == cfgValue.Id)
.SingleOrDefault();
But I get this exception:
NHibernate.QueryException was unhandled by user code
Message=could not resolve property: ConfigValues of: Domain.Model.Key
I figure it's because the Key object is declared in a way to restrict access to IList and mapped with a non-visible property.
public class Key
{
public virtual int Id { get; protected set; }
public virtual IEnumerable<ConfigValue> ConfigValues { get { return _configValues; } }
private IList<ConfigValue> _configValues = new List<ConfigValue>();
...
And mapped by code as:
Bag<ConfigValue>("_configValues", attr => {
attr.Lazy(CollectionLazy.Lazy);
attr.Inverse(false);
attr.Cascade(Cascade.None);
}, cm => cm.ManyToMany());
The question: How can I do it using NHibernate API?
The only way I managed to do it is with HQL:
IList<Key> keys = session.CreateQuery(#"select K_
from Key as K_
left outer join K_._configValues as KO_
where KO_.Id = :cfgValueId ")
.SetParameter("cfgValueId", cfgValue.Id)
.List<Key>();
I'm not firm with mapping by code but something along the lines
Bag<ConfigValue>("ConfigValues", attr => {
attr.Access("field.camelcase-underscore");
}, cm => cm.ManyToMany());
or Fluent NHibernate (if someones interested)
HasMany(x => x.ConfigValues)
.Access.CamelCaseField(Prefix.Underscore);