passing object with collection from domain to modelDTO with nhibernate - nhibernate

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();

Related

AutoMapper - Map destination object's property that does not exist in source object

I'm casting class Process to ProcessDTO. The ProcessDTO object have a property named ProcessSteps that does not exist in Process. I want the ProcessSteps-property to be casted to ProcessStepsDto. I'm using a global configuration for AutoMapper.
I have tried using
CreateMap<Process, ProcessDto>()
.ForMember(dest=>dest.Steps, opt => opt.MapFrom(s => Mapper.Map<ProcessStepDto>(s)));
But this is wrong..
public class Process
{
}
public class ProcessDto
{
//This property does not exists in source object and get's created on get. I want this to be cast to "ProcessStepDto[]"
public ProcessStep[] Steps
{
get
{
ProcessStepRepository repository = new ProcessStepRepository();
return repository.Select(x => x.ProcessId == this.Id && x.Active).OrderBy(x=>x.Position).ToArray();
}
}
}
public class ProcessStep
{
...
}
public class ProcessStepDto
{
...
}
UPDATE
After i use AutoMapper to mapp my object Process to ProcessDto i also want the property of Stepsto be mapped to ProcessStepsDto. Currently it stays as ProcessStep.
If you want to return ProcessDto with ProcessStepDto[], the ProcessDto should define the property with type ProcessStepDto[] instead of ProcessStep[].
public class ProcessDto
{
public ProcessStepDto[] Steps
{
get
{
ProcessStepRepository repository = new ProcessStepRepository();
return repository.Select(x => x.ProcessId == this.Id && x.Active).Select(s => new ProcessStepDto { PropertyInProcessStepDto = s.PropertyInProcessStep }).OrderBy(x=>x.Position).ToArray();
}
}
}

NHibernate Dynamic Component Default Value Issue

All of my entities (that are mapped to a database table) inherit from an entity class with a dynamic component on it called Attributes e.g.:
public abstract class Entity<T> {
public virtual T Id { get; set; }
private IDictionary _attributes;
public virtual IDictionary Attributes {
get { return _attributes ?? (_attributes = new Hashtable()); }
set { _attributes = value; }
}
}
The Attributes collection allows me to add extra fields to each entity without directly changing the entity itself. This allows me to make my application more modular.
For example say I have the following entity:
public class User : Entity<int> {
public virtual string Name { get; set; }
}
Now say I have a Forum module which needs a NumPosts property against the User. I would add the field against the Users table in the database. This field is non nullable and has a default value of 0. I then map the field using the dynamic component against the User entity.
However when I try inserting the user by saying:
session.Save(new User() { Name = "Test" });
It throws an error as it's expecting me to set a value for NumPosts and the generated SQL would be something like:
INSERT INTO Users (Name, NumPosts) VALUES ('Test', NULL)
However NumPosts does not allow nulls and hence the error. Ideally I'd like it to say the following if the Attributes collection does not contain an entry for NumPosts:
INSERT INTO Users (Name) VALUES ('Test')
An alternative is to say the following which would work fine:
session.Save(new User() { Name = "Test", Attributes = new Hashtable() { { "NumPosts", 0 } } });
The problem I have is that I don't want the modules to have a dependency on each other and I can't really say this.
For reference here's a bare bones version of session factory method which maps the NumPosts field:
return Fluently.Configure()
...
.ExposeConfiguration(c => {
// Get the persistent class
var persistentClass = c.GetClassMapping("User");
// Create the attributes component
var component = new Component(persistentClass);
// Create a simple value
var simpleValue = new SimpleValue(persistentClass.Table);
// Set the type name
simpleValue.TypeName = "Int32";
// Create a new db column specification
var column = new Column("NumPosts");
column.Value = simpleValue;
column.Length = 10;
column.IsNullable = false;
column.DefaultValue = "0";
// Add the column to the value
simpleValue.AddColumn(column);
// Ad the value to the component
component.AddProperty(new Property() { Name = column.Name, Value = simpleValue });
// Add the component property
persistentClass.AddProperty(new Property() { Name = "Attributes", Value = component });
})
.BuildConfiguration();
I'd appreciate if someone could let me know if this is possible. Thanks
You know how to make it working as described above:
... An alternative is to say the following which would work fine:
session.Save(new User()
{
Name = "Test", Attributes = new Hashtable() { { "NumPosts", 0 } }
});
... The problem I have is that I don't want the modules to have a dependency on each other and I can't really say this...
In case, that the biggest issue is the explicit Attributes initialization ("...I don't want the modules to have a dependency...") we can use:
12.2. Event system
So, with Listener like this:
[Serializable]
public class MyPersistListener : NHibernate.Event.ISaveOrUpdateEventListener
{
public void OnSaveOrUpdate(SaveOrUpdateEvent #event)
{
var entity = #event.Entity as Entity<int>; // some interface IHaveAttributes
if (entity == null) // would be more appropriate
{
return;
}
var numPosts = entity.Attributes["NumPosts"] as int?;
if (numPosts.HasValue)
{
return;
}
entity.Attributes["NumPosts"] = 0;
}
}
Based on this doc snippet:
Configuration cfg = new Configuration();
ILoadEventListener[] stack = new ILoadEventListener[] { new MyLoadListener(), new DefaultLoadEventListener() };
cfg.EventListeners.LoadEventListeners = stack;
This should be the init in our case:
.ExposeConfiguration(c => {
var stack = new ISaveOrUpdateEventListener [] { new MyPersistListener() };
c.EventListeners.SaveEventListeners= stack;

Deleted object resaved by cascade - self referencing table

I'm getting the following error:
"deleted object would be re-saved by cascade (remove deleted object from associations)"
I have trimmed the entirety of the ajax call to the following:
[HttpPost]
[UnitOfWork(Scope = FilterScope.Result)]
public ActionResult SaveEditMode(long id, AddTrackedRowViewModel model, string editMode, List<string> elementNames, string provisionData)
{
var cell = _supplementCoordinator.GetSupplement(id).TrackedTables.First(x => x.Name == model.Name).TrackedRows.First(x => x.Ordinal == model.Ordinal).TrackedCells.First(x => x.Name == "Detail");
_supplementCoordinator.RemoveChildren(cell);
return Json( new {Success = true});
}
public bool RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in parentNode.ChildNodes)
{
_trackedFieldRepository.Delete(trackedField);
}
return true;
}
My mappings are as follows
mapping.HasMany(x => x.ChildNodes).KeyColumn("ParentNodeId").Inverse();
mapping.References(x => x.ParentNode);
Just remove the child nodes from the parent collection just as the error suggests:
public bool RemoveChildren(TrackedNode parentNode)
{
foreach (TrackedField trackedField in new List<TrackField>(parentNode.ChildNodes))
{
_trackedFieldRepository.Delete(trackedField);
_parentNode.Remove(trackField);
}
return true;
}

NHibernate: how to use ExpandoResultTransformer with Session.QueryOver

I have created a ExpandoResultTransformer, that would be used for converting query results to dto to be used in UI. It looks like this:
public class ExpandoResultTransformer : IResultTransformer
{
public object TransformTuple(object[] tuple, string[] aliases)
{
dynamic toReturn = new ExpandoObject();
for (int i = 0; i < aliases.Length; i++)
{
((IDictionary<string, object>)toReturn)[aliases[i]] = tuple[i];
}
return toReturn;
}
public IList TransformList(IList collection)
{
return collection;
}
}
When I use it against a createSQLQuery it works perfectly
var contacts = _session.CreateSQLQuery("exec up_ClientContactsView #ClientId=:clientId")
.SetParameter("clientId", request.ClientId)
.SetResultTransformer(new ExpandoResultTransformer())
.List<dynamic>();
However, when I tried using it against QueryOver, I run into trouble. There are no aliases sent in. So I tried specifying them like this
var periodTables = _session.QueryOver<PeriodTable>()
.Where(x => x.ClientPolicy.Id == request.ClientPolicyId)
.SelectList(list => list
.Select(i =>i.Id).WithAlias(()=>info.PeriodTableId) !! does not work as dynamic cannot be used in expression error
.Select(i => i.Id).WithAlias(()=>"PeriodTableId" !!! does not work either as it cannot find member error
.TransformUsing(new ExpandoResultTransformer())
.List<dynamic>();
Any ideas how to pass in aliases?
You can use an anonymous type instance to act as a template object:
var template = new { PeriodTableId = 0 };
var periodTables = _session.QueryOver<PeriodTable>()
.Where(x => x.ClientPolicy.Id == request.ClientPolicyId)
.SelectList(list => list
.Select(i =>i.Id).WithAlias(()=>template.PeriodTableId)
)
.TransformUsing(new ExpandoResultTransformer())
.List<dynamic>();

Fluent nHibernate - Query Cache not working with "session.Query" and Cacheable()

Did't get any QueryCacheHitCount. The Source Code looks like:
nHibernate 3.3.1.4000, FluentnHibernate 1.3.0.733
Config:
Factory = Fluently.Configure()
.ExposeConfiguration(c =>
c.SetProperty(NHibernate.Cfg.Environment.GenerateStatistics, "true"))
.Database(
MsSqlConfiguration.MsSql2008.ConnectionString(c => c.Is("DATA SOURCE=localhost;PERSIST SECURITY INFO=True;USER ID=AAA;Password=AAA"))
.ShowSql()
)
.Mappings(x => x.FluentMappings.AddFromAssemblyOf<Localization.NHibernate.Article>())
.ExposeConfiguration(BuildDatabase)
.Cache(
x => x.UseSecondLevelCache()
.UseQueryCache()
.ProviderClass<NHibernate.Cache.HashtableCacheProvider>())
.BuildSessionFactory();
Execute:
using (var tx = session.BeginTransaction())
{
Factory.Statistics.Clear();
for (int i = 0; i < 10; i++)
{
Article s = session.Query<Article>().Cacheable().Where(x => x.Name == "O").SingleOrDefault();
}
Console.WriteLine(Factory.Statistics.QueryCacheHitCount);
Console.WriteLine(Factory.Statistics.SecondLevelCacheHitCount);
Console.WriteLine(Factory.Statistics.QueryExecutionCount);
}
Only if i change the cache config and add my private QueryCacheFactory with .QueryCacheFactory<CacheFactory>() than it works (But i ignore the IsUpToDate check)
public class CacheFactory : NHibernate.Cache.IQueryCacheFactory
{
public NHibernate.Cache.IQueryCache GetQueryCache(string regionName, NHibernate.Cache.UpdateTimestampsCache updateTimestampsCache, Settings settings, IDictionary<string, string> props)
{
return new MyStandardQueryCache(settings, props, updateTimestampsCache, regionName);
}
private class MyStandardQueryCache : NHibernate.Cache.StandardQueryCache
{
public MyStandardQueryCache(Settings settings, IDictionary<string, string> props, NHibernate.Cache.UpdateTimestampsCache updateTimestampsCache, string regionName)
: base(settings, props, updateTimestampsCache, regionName) { }
protected override bool IsUpToDate(Iesi.Collections.Generic.ISet<string> spaces, long timestamp)
{
return true; // SET TO TRUE than it's hitting the cache
}
}
}
I had a similar problem and from a I can remember, the results of the query are cached using the creation time of the session or the transaction. Try using a session for first query and different one for the following, doing that you should receive cache hits.