I have this NHibernate model:
public class RootTable
{
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual DateTime? Start { get; set; }
public virtual DateTime? Finish { get; set; }
public virtual IList<Leaf1> ChildCollection1 { get; set; }
}
public class Leaf1
{
public virtual int ID { get; set; }
public virtual string Info1 { get; set; }
public virtual string Info2 { get; set; }
public virtual RootTable Parent { get; set; }
}
public class RootMapping : ClassMap<RootTable>
{
public RootMapping()
{
Table("RootTable");
Id(c => c.Name);
Map(c => c.Description, "Desc").Length(20);
Map(c => c.Start).Length(20);
Map(c => c.Finish).Length(20);
HasMany(c => c.ChildCollection1)
.Cascade.All()
.LazyLoad()
.Inverse();
}
}
public class Leaf1Mapping : ClassMap<Leaf1>
{
public Leaf1Mapping()
{
Table("LeafTable1");
Id(c => c.ID, "RowID").GeneratedBy.Identity();
Map(c => c.Info1).Length(20);
Map(c => c.Info2).Length(20);
References(c => c.Parent).Column("Parent").LazyLoad();
}
}
What I'm trying to do is access the value of the referenced column in Leaf1 without lazyloading RootTable.
In otherwords, I have this:
this.LogMessage("Loading leaves...");
var allleafs = session.CreateCriteria(typeof(Leaf1)).List<Leaf1>();
this.LogMessage("Loaded leaves...");
var leaf = allleafs[0];
this.LogMessage("Leaf metadata:");
// This causes a lazy-load of the RootTable object.
this.LogMessage("Leaf parent is " + leaf.Parent);
Now what I actually want is the value of "Parent" as it's stored in the underlying database - I don't care for the parent object, I don't want to load it, all I want to do get the raw value. I can't access the field that contains the value (i.e., leaf.Parent.Name) as I want this to work in a generic fashion...
[Background]
Ultimately this is plugging into an auditing framework that uses an NHibernate interceptor, so this needs to work in a generic way so that for any object passed in I can report on the changed values. It's entirely possible the child node will have changed with no change to the root node, so when the interceptor's OnFlushDirty() is called, I do not want the interceptor to cause a lazy-load of other objects.
I know I can reference the parent property directly (e.g., I can say "leaf.Parent.Name") and this will get me the value without the lazy load, but there doesn't seem to be a quick way to determine that "Name" is the key property I want to return.
[Edited to add...]
Walking the tree doesn't seem to work as I get a null reference exception:
var theType = leaf.Parent.GetType();
// This line returns a NULL due to the proxy class.
var metadata = factory.GetClassMetadata(theType);
var idProp = metadata.IdentifierPropertyName;
var prop = theType.GetProperty(idProp);
var val = prop.GetValue(leaf.Parent, null);
this.LogMessage("Leaf parent is " + val);
Now, theType comes back as RootTableProxy, so is just a placeholder because the main class isn't loaded. Which means metadata is null as there is no class metadata and thus idProp fails with a null reference exception.
So I can't actually see how to get referenced column value without a lazy load somewhere along the way: surely this can't be right?
Edited to add (more!)
I thought an easy solution have been found by using session.GetIdentifier(). However this doesn't seem to work in all cases: in an interceptor calling session.GetIdentifier(state[i]) on some objects caused an exception stating that the object wasn't part of the current session, so still looking for a more reliable solution that doesn't resort to reflection. Any ideas welcome...
What about adding another property to your Leaf1 Class.
e.g.
public class Leaf1
{
public virtual int ID { get; set; }
public virtual string Info1 { get; set; }
public virtual string Info2 { get; set; }
public virtual RootTable Parent { get; set; }
public virtual int ParentId { get; set; }
}
and then map is as readonly
public class Leaf1Mapping : ClassMap<Leaf1>
{
public Leaf1Mapping()
{
Table("LeafTable1");
Id(c => c.ID, "RowID").GeneratedBy.Identity();
Map(c => c.Info1).Length(20);
Map(c => c.Info2).Length(20);
References(c => c.Parent).Column("Parent").LazyLoad();
Map(c => c.ParentId).Column("Parent").ReadOnly();
}
}
You could make your entities implement interfaces (would be cleaner), but if you want to get the actual type of the proxy, use NHibernateUtil.GetClass(proxy) which will return the underlying type that you're looking for
It transpires that ISession has a method GetIdentifier which returns the cached value of a property. Taking the example from my original post, the code changes to this:
this.LogMessage("Loading leaves...");
var allleafs = session.CreateCriteria(typeof(Leaf1)).List<Leaf1>();
this.LogMessage("Loaded leaves...");
var leaf = allleafs[0];
this.LogMessage("Leaf metadata:");
this.LogMessage("Leaf parent is " + session.GetIdentifier(leaf.Parent));
Now in testing this seems to be working nicely, and can be called from OnFlushDirty within an interceptor on both currentState and previousState objects.
I've been trying to find more information on GetIdentifier, but the NH documentation is sadly lacking, and there seems to be few, if any, blog posts that mention this. If anyone is aware of any caveats, problems, or "funnies" that I need to know aobut in relation to this method, I'd be more than happy to hear about them...
[Edited to add]
...as it turns out, this doesn't work if the object isn't a proxy object(!). Still looking for a reliable method to do this that doesn't involve reflection or decorating objects with attributes to identify fields.
Related
I have a simple problem - I would like one of the RESTful endpoints serve a resource DTO (auto-mapped) with its related resources as their IDs only. However there does not seem to be any way to implement it without loading the whole(and heavy) related entities. Consider following (DB first) example model:
public partial class Blog
{
public int Id { get; set; }
public string Url { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public partial class Post // some heavy entity
{
public int Id { get; set; }
public string Content { get; set; }
// other properties
}
and its corresponding DTO
// api/v1/blogs serves collection of following type
public class BlogSlimDto
{
public int Id { get; set; }
public string Url { get; set; }
public int[] PostIds { get; set; }
}
a straightforward soltion would be to fetch all the related Posts from database and discard all data except for the IDs, but that can be inefficient or even unfeasible depending on related Post entity size:
var result = ctx.Blogs.Include(blog => blog.Posts) //fecth everything and discard it on next line
.Select(blog => _mapper.Map<BlogSlimDto>(blog));
// simply use a profile that discards Posts but keeps their Ids, e.g.
// .forMember(dto => dto.PostIds, opt => opt.MapFrom(db.Posts.Select(p => p.Id)))
there is similar question which offers a solution using anonymous types, however this does not play well with Automapper at all:
var result = ctx.Blogs.Select(blog => new {
blog.Id,
blog.Url,
PostIds = blog.Posts.Select(b => b.Id),
}).Select(ablog => _mapper.Map<BlogSlimDto>(ablog)); //throws, no mapping and such mapping cannot be defined
The code above will throw during runtime because there no Automapper mapping defined. Even worse, it cannnot be defined because there is no support for anonymous types in Automapper. Moreover, solutions with one-by-one 'manual' property assignment tend to be difficult to maintain.
Is there an alternative solution that would allow EF query without fetching whole related entities while allowing the result to be auto-mapped to the BlogSlimDto?
You can use the queryable extensions:
https://docs.automapper.org/en/stable/Queryable-Extensions.html
var configuration = new MapperConfiguration(cfg =>
cfg.CreateMap<OrderLine, OrderLineDTO>()
.ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)));
public List<OrderLineDTO> GetLinesForOrder(int orderId)
{
using (var context = new orderEntities())
{
return context.OrderLines.Where(ol => ol.OrderId == orderId)
.ProjectTo<OrderLineDTO>().ToList();
}
}
Replacing the Item and OrderLine with your Post and Blogs
I have an edit model and an EF entity, and I want to use AutoMapper to update the entity with data from the edit model. This works very well:
// The classes involved
public class FooEditModel {
public Guid Id { get; set; }
public string FooProp { get; set; }
}
public class FooEntity {
public Guid Id { get; set; }
public string FooProp { get; set; }
}
// Mapper configuration
cfg.CreateMap<FooEditModel, FooEntity>();
// Actual usage
var entity = _dbContext.Foos
.Where(e => e.Id == id) // id is a route param
.Single();
_mapper.Map(editModel, entity); // editModel is populated by model binding
_dbContext.SaveChanges();
Now, entity has been updated with the value of FooProp from editModel. All is well.
However, when I add a collection of a different type, it doesn't work as well as I'd like anymore
// classes
pulic class FooEditModel {
public Guid Id { get; set; }
public IReadOnlyCollection<BarEditModel> Bars { get; set; }
}
public class BarEditModel {
public Guid Id { get; set; }
public string BarProp { get; set; }
}
public class FooEntity {
public Guid Id { get; set; }
public IReadOnlyCollection<BarEntity> Bars { get; set; }
}
public class BarEntity {
public string BarProp { get; set; }
}
// mapper config
cfg.CreateMap<FooEditModel, FooEntity>();
cfg.CreateMap<BarEditModel, BarEntity>();
// usage
var entity = _dbContext.Foos
.Include(f => f.Bars)
.Where(f => f.id == id)
.Single();
_mapper.Map(editModel, entity);
_dbContext.SaveChanges();
Now, instead of updating the FooEntity as well as all related BarEntitys, it gives me an InvalidOperationException with the following message:
InvalidOperationException: The instance of entity type 'BarEntity' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
How do I configure AutoMapper so that the mapper will re-use the objects in the existing collection, rather than try to replace them?
Thomas
You should understand what is going on under the hood. AutoMapper is a simple tool, which uses reflection to avoid line by line property rewriting. Nevertheless, it is creating new objects from time to time (as in your example with collections) Without collections the mapper is not creating anything - it is simply rewriting properties. If you add collections, he is handling this by creating new collections to map new collections from editModel.
This creates a situation where on DataContext, you have instantiated objects of type Bar and mapper is creating a new one which leads to conflict.
As Ivan Stoev stated, you can use Automapper.Collection to handle such situation (i never used it though, but it probably address this problem)
I have this scenario
public class ObjectViewModel
{
public int Isn { get; set; }
public int? APO_Isn { get; set; }
public string Name { get; set; }
}
public class ObjectModel
{
public int Isn { get; set; }
public int? APO_Isn { get; set; }
public int? CLI_Isn { get; set; }
public int? EMP_Isn { get; set; }
}
//Configuration of AutoMapper: Mapper.CreateMap<ObjectViewModel, ObjectModel>().ReverseMap();
//On my controller: var objectModel= Mapper.Map<ObjectModel>(objectViewModel);
Suppose Isn property of objectViewModel equals 53. When I map the objectModel based on objectViewModel, for some reason unknown to me, the AutoMapper are entering the value 53 in Isn (That's ok), but are entering in CLI_Isn and EMP_Isn too and my ViewModel doesn't have those properties.
I did some test, changed CLI_Isn to Cli_Isn and EMP_Isn to Emp_Isn and the problem was solved. But I still do not understand what was causing the problem, that is why 53 is been moved to CLI_Isn and EMP_Isn.
What is happening is that you are triggering AutoMapper's Flattening feature
From that documentation:
When you configure a source/destination type pair in AutoMapper, the configurator attempts to match properties and methods on the source type to properties on the destination type. If for any property on the destination type a property, method, or a method prefixed with "Get" does not exist on the source type, AutoMapper splits the destination member name into individual words (by PascalCase conventions).
Since issue 402 was fixed this appears to be working with underscores as well, which is what you're seeing here.
There is seemingly no way to disable flattening, so you would have to explicitly exclude those properties:
Mapper.CreateMap<ObjectViewModel, ObjectModel>()
.ForMember(dest => dest.CLI_Isn, src => src.Ignore())
.ForMember(dest => dest.EMP_Isn, src => src.Ignore())
.ReverseMap();
Is it possible your Mapper.Initialize includes a call to RecognizeDestinationPrefixes("CLI_") or RecognizePrefixes("CLI_")? This would make automapper ignore "CLI_" when mapping types but not ignore "Cli_".
So I am rather new to using NHibernate for database access and after studying its usage elsewhere in an application I am editing, I cannot seem to get it to work for me and I do not know why. Effectively, I am trying to populate an object with data from my database so that I can pull pieces in and present them to the user. The issue is that despite my syntax and code looking correct, my object remains null after query execution.
The class that is being used to represent the table in the database:
public class AllocateLog
{
public virtual string UserName { get; set; }
public virtual int Id { get; set; }
public virtual int OwnerId { get; set; }
public virtual int MemberId { get; set; }
public virtual int? ResId { get; set; }
public virtual string RequestComments { get; set; }
public virtual DateTime DateEntered { get; set; }
public virtual DateTime? DateExited { get; set; }
public virtual string EntryAccessPoint { get; set; }
public virtual string ExitAccessPoint { get; set; }
}
The mapping code:
public class AllocateLogOverride : IAutoMappingOverride<AllocateLog>
{
public void Override(AutoMapping<AllocateLog> map)
{
#if LOCAL_INSTALL
map.Schema("dbo");
#else
map.Schema("cred");
#endif
map.Table("allocate_log");
map.CompositeId()
.KeyProperty(x => x.OwnerId, "owner_id")
.KeyProperty(x => x.MemberId, "member_id")
.KeyProperty(x => x.DateEntered, "date_entered");
map.Map(x => x.UserName).Column("user_name");
map.Map(x => x.ResId).Column("res_id");
map.Map(x => x.RequestComments).Column("request_comments");
map.Map(x => x.DateExited).Column("date_exited");
map.Map(x => x.EntryAccessPoint).Column("entry_access_point");
map.Map(x => x.ExitAccessPoint).Column("exit_access_point");
}
}
The query code:
public class AllocateLogsForAccessPoints : IQuery<IQueryOver<AllocateLog>, AllocateLog>
{
private readonly string accessPoint;
public AllocateLogsForAccessPoints(string accessPoint)
{
this.accessPoint = accessPoint;
}
public IQueryOver<AllocateLog> BuildQuery(ISession session)
{
return session.QueryOver<AllocateLog>()
.Where(d => d.EntryAccessPoint == accessPoint);
}
public AllocateLog Execute(IQueryOver<AllocateLog> query)
{
return query.SingleOrDefault();
}
}
And the code that I am using just as a test to see if my query will return anything to my object:
var asdf = DbQueryExecutor.ExecuteQuery(new AllocateLogsForAccessPoints((string)"north gate"));
There is only one record in the database that fits that query as it is the only row in the database with any data in entry_access_point and the string is "north gate". The table is cred.allocate_log and all of the columns are named correctly in the mapping file. Furthermore, removal of the mapping file or rather commenting it out does not result in any runtime error which means to me that NHibernate never even tries to use the mapping file because if I try to do this (meaning commenting out the contents of the file) with any other mapping file who's query works, I get a runtime error. So I am entirely stumped as to why my object remains null after executing the query which runs without error. Any ideas? I will update my original post if you require more information.
You might try downloading an application called NHProf from Hibernating Rhinos. It traces all nHibernate calls and shows you exactly the SQL that nHibernate is trying to run. It has been a godsend for me in trying to figure out just what the hell nHibernate is trying to do.
The answer to my issue was to check the namespace and location of the various files associated with the query. I had originally put one of the files in the wrong folder and so the namespace was mapped to that incorrect folder. I moved the file into the correct folder but never changed the namespace. So in the override file I had a reference to the folder the namespace said it was part of when in actuality it was not. Correcting these errors was the solution.
I don't know how to phrase this properly. I'm working with a pre-existing domain, where certain entities can contain children that are versioned. The children are responsible for their own version number, but ultimately, this version number only makes sense in context of the attached parent entity.
public class Blog
{
public virtual IList<VersionedItem> VersionedItems { get; set; }
public virtual CurrentVersionedItem {
get {
return VersionedItems.OrderByDescending(x => x.Version).FirstOrDefault();
}
}
}
public class VersionedItem
{
public virtual Blog { get;set; }
public virtual int Version { get; set; }
public virtual string Content { get; set; }
public virtual int SomeNumber { get; set; }
}
And what I'd like to achieve:
var blogs = Session.Query<Blog>(x=> x.CurrentVersionedItem.SomeNumber == 5)
While the IQueryable provider of NHibernate is forgiving, I will not eat everything. Is there a way to define a (fluent) mapping that resolves the "CurrentVersionedItem" property properly?
I'm also aware of the fact CurrentVersionedItem could potentially return null in this scenario (if it worked in the first place).
Why won't you do like this:
var item = session.Query<VersionedItem>().FirstOrDefault(q => q.SomeNumber == 5);
Blog blog;
if (item != null)
blog = item.Blog;