Map properties by nameing convention - automapper

I am using automapper to map some objects between the database and another representation.
The entity looks something like
public class MyEntity {
public int Id { get; set; }
public Guid RowId { get; set; }
}
public class MyObject {
public Guid Id { get; set; }
}
As you can see, the names and types are unaligned.
Since I got many Entities and Objects, I'd rather not CreateMap<A, B>().ForMember(d => d.Id, mex => mex.MapFrom(s => s.RowId));.
To not having to do the above Convention:
AddMemberConfiguration()
.AddMember<NameSplitMember>()
.AddName<ReplaceName>(_ => _.AddReplace("RowId", "Id"));
This does not what I suspected it to do and I was not able to figure out, how to use the ReplaceName Convention.
So I'd like to hear ideas about how to map that types.
MyEntity and MyObject both are base types, so I could also use that.
What I'm trying to archieve in pseudo-code:
if(source is MyEntity && target is MyObject)
{
target.Id = source.RowId;
}
ForAllMembers
On recommendation of #lucian-bargaoanu I tried looking into ForAllMembers.
I did the following in the MapperProfile:
public class MapperProfile : Profile {
public MapperProfile() {
ForAllMaps(MapEntityBaseId);
}
protected void MapEntityBaseId(TypeMap map, IMappingExpression mex)
{
if (!map.SourceType.IsSubclassOf(typeof(EntityBase)))
return;
if (!map.DestinationType.IsSubclassOf(typeof(MyObject)))
return;
mex.ForMember("Id", opt => opt.MapFrom("RowId"));
}
}
also the debugger hints me, that ForAllMember is executed as expected, it still fails the mapping.
I created a GIST for the ForAllMembers: https://gist.github.com/anonymous/511a1b69b795aa2bc7e7cd261fcb98b1

Related

How to apply AutoMapper ValueConverters against properties that may be null

I think this is an AutoMapper bug but the issue template they have in GitHub states to post something to SO first.
I want to be able to apply an IValueConverter without worrying about null exceptions.
As an example, I'm using a IValueConverter to apply some logic in multiple mappings:
public class ExampleConverter : IValueConverter<string, string>
{
public string Convert(string sourceMember, ResolutionContext context)
{
if (string.IsNullOrEmpty(sourceMember))
{
return string.Empty;
}
return sourceMember.ToUpper();
}
}
If I have the following types I'm mapping to and from:
public class ExampleSource
{
public ExampleNestedSource1 A { get; set; }
}
public class ExampleNestedSource1
{
public ExampleNestedSource2 B { get; set; }
}
public class ExampleNestedSource2
{
public string Input { get; set; }
}
public class ExampleDestination
{
public string Output { get; set; }
}
I can apply the converter like so:
public class ExampleProfile : Profile
{
public ExampleProfile()
{
this.CreateMap<ExampleSource, ExampleDestination>(MemberList.None)
.ForMember(dst => dst.Output, opt => opt.ConvertUsing(new ExampleConverter(), src => src.A.B.Input));
}
}
Using the converter like this however, throws an exception when A or B are null:
mapper.Map<ExampleDestination>(new ExampleSource // Throws null reference
{
A = new ExampleNestedSource1(),
});
mapper.Map<ExampleDestination>(new ExampleSource()); // Throws null reference
Because opt.ConvertUsing(new ExampleConverter(), src => src.A.B.Input) takes in an expression I can't use null propagating operators like src?.A?.B?.Input.
If I remove the converter and use MapFrom, the problem goes away:
public class ExampleProfile : Profile
{
public ExampleProfile()
{
this.CreateMap<ExampleSource, ExampleDestination>(MemberList.None)
.ForMember(dst => dst.Output, opt => opt.MapFrom(src => src.A.B.Input));
}
}
But MapFrom doesn't support IValueConverter it only supports IMemberValueResolver which is less reusable.
Is there a better way to handle this scenario?
If not, I'm thinking AutoMapper should either:
Add support for IValueConverter in the MapFrom method.
ConvertUsing should not invoke the given converter when the expression doesn't resolve to a property and not throw.
ConvertUsing should invoke the given converter with null when the expression doesn't resolve to a property (in the same way that MapFrom) and not throw.

Mapping int (Id) to Model instance

I often have Models and Dtos like this:
// DTO class
public class OrderDto
{
public int Id { get; set; }
public DateTime Date { get set; }
public int ProductId { get; set; }
// ...
}
// Model class
public class Order : BaseObject
{
public DateTime Date { get set; }
public Product Product { get; set; }
// ...
}
// Model class
public class Product : BaseObject
{
// ...
}
In order to map my OrderDto to the Order class I have to configure AutoMapper for this particular "association" like so:
CreateMap<OrderDto, Order>()
.ForMember(m => m.Product, d => d.ResolveUsing((d, m) =>
{
return m.Session.GetObjectByKey<Product>(dto.ProductId);
}))
This is quite cumbersome to do this for each case like this. Therefore I was looking into generalizing this behaviour by using a custom TypeConverter class:
public class IntBaseObjectTypeConverter : ITypeConverter<int, BaseObject>
{
private UnitOfWork uow;
// ...
public BaseObjectConvert(int source, BaseObject destination, ResolutionContext context)
{
return uow.Session.GetObjectByKey(typeof(destination), source);
}
}
However, this will fail of course if the destination is null. Unfortunately the ResolutionContext does not give me any clue about the specific type of the destination property.
Now my question is, if there is another way to achieve with AutoMapper what I would like to do?
Please note that I am not using Entity Framework which of course would solve this issue on the model level with foreign key and navigational properties. I use XPO from DevExpress which does not allow foreign key and navigational properties like in Entity Framework.

inherited class AutoMapper.AutoMapperMappingException

I am new at automapper and it is a very good stuff easy to use, but now I have a problem with it. Trying to convert my derived class to base and it gives me
AutoMapper.AutoMapperMappingException
Missing type map configuration or unsupported mapping.
Mapping types: ClientEventDb -> EventId
Database.ClientEventDb -> EventId
Destination path: ClientEvent
Source value:
Event:Login
Automapper wants to convert ClientEventDb to EventId? I don't understand why. EventId is an enum...
Please help me I have run out of ideas.
Here is the code which I run:
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>();
Console.WriteLine("hello");
return edbl.Select(edb => Mapper.Map<ClientEvent>(edb)).ToArray();
Here are my classes
[Table("events", Schema = "public")]
public class ClientEventDb : ClientEvent
{
public ClientEventDb(string userName, EventId happening, object userObject = null)
: base(userName, happening, userObject)
{
}
public ClientEventDb()
{
}
}
[ProtoContract]
[Table("events", Schema = "public")]
public class ClientEvent : ClientEventBase
{
[ProtoMember(1)]
[Column("username")]
public string UserName { get; private set; }
[ProtoMember(2)]
[Column("time")]
public DateTime DateTime { get; private set; }
[ProtoMember(3)]
[Key]
[Column("id")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; private set; }
[ProtoMember(4)]
[Column("data")]
public byte[] UserObject { get; set; }
public ClientEvent(string userName,EventId happening, object userObject=null) : base(happening)
{
UserName = userName;
DateTime = DateTime.Now;
//UserObject = null;
if (userObject!=null) throw new NotImplementedException();
}
public ClientEvent()
{
}
protected ClientEvent Clone()
{
return (ClientEvent)MemberwiseClone();
}
}
[ProtoContract]
[ProtoInclude(10, typeof(ClientEvent))]
public class ClientEventBase
{
[Column("eventid")]
[ProtoMember(1)]
public int EventIdValue { get; set; } //must be public because of entity framework
[NotMapped]
public EventId EventId
{
get { return (EventId) EventIdValue; }
set { EventIdValue = (int) value; }
}
public ClientEventBase(EventId eventId)
{
EventId = eventId;
}
public ClientEventBase()
{
}
public override string ToString()
{
return String.Format("Event:{0}",EventId);
}
}
public enum EventId
{
Login = 1,
Logout,
ExitApplication,
}
UPDATE
bugfix: ClientEvent [Key] attribute moved to id property
Solution was this (thx to stuartd):
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.ToArray();
}
Mapper.CreateMap<ClientEventDb, ClientEvent>().ConstructUsing((ClientEventDb src) => new ClientEvent());
return edbl.Select(Mapper.Map<ClientEvent>).ToArray();
AutoMapper is confused as its made to map between similar properties in different classes, you are using it incorrectly - you just need to go from the derived class to the base which does not require AutoMapper. You could use this to do what you need....
ClientEventDb[] edbl;
using (var context = new DbEntities())
{
edbl=context.Events.Take(1000).ToArray();
}
return edbl.Cast<ClientEvent>().ToList();
I'd be looking at why you even feel you need a derived ClientEventDb though - understand we dont have the whole picture here but it seems to do nothing in addition to what the base class already does.
The issue is that ClientEvent has two constructors but you have not told AutoMapper which to use.
If you want it to use your constructor with parameters, change your mapping code to this and it will work:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing(src => new ClientEvent(src.UserName, src.EventId));
Or to make AutoMapper use the default constructor:
Mapper.CreateMap<ClientEventDb, ClientEvent>()
.ConstructUsing((ClientEventDb src) => new ClientEvent());

I'm sorry, but it's another: Found shared references to a collection

Recently I've been adding some features to my local project, and I'm struggling with this one part. The short of it is NHibernate give me the line:
Found shared references to a collection: Page.Menus
The simple part of it is, I only want to save the relational map that binds menus to pages, which you can see the Reference below in PageMap. I should add that loading data works great, it's the saving that's killing me.
I spent a lot of time yesterday digging through here, and the good ole web, trying to find the answer, and I just kept striking out. Maybe it's bad searching on my part, but I feel I tried everything. If you know where it is can you please supply it? (thanks)
For the actual details, I've tried to simplify what's going on. I've added the PageReposity, UnitOfWork, and the proxy objects, as well as their mappings.
Where I get hazy on is the cascade, and how to save the relationship table (many to many)
For the first part, here is what happens when I save (Add). I've actually done this a few ways within the PageRepository. Since I'm strugging with the Add(), I've included it here:
public override bool Add(Page entity)
{
UnitOfWork.Save(entity);
/* I have also tried doing the following below, which doesn't help
for (var index = 0; index < entity.Menus.Count; index++)
{
UnitOfWork.Save(entity.Menus[index]);
}
*/
UnitOfWork.Commit(); // bam, error!
return true;
}
In the UnitOfWork I've setup the following in the ctor (the ISession is injected each time via ninject like so:
// DomainModule
...
Bind<ISFactory>().To<NHibernateSessionFactory>()
.InSingletonScope()
.WithConstructorArgument("connectionString", _connectWeb);
...
// Back to the UnitOfWork
...
private ISession Session { get; set; }
...
public UnitOfWork(ISFactory sessionFactory)
{
_sessionFactory = sessionFactory.GetSessionFactory();
Session = _sessionFactory.OpenSession();
Session.FlushMode = FlushMode.Never; // I have also tried FlushMode.Commit
_transaction = Session.BeginTransaction(IsolationLevel.ReadCommitted);
}
...
public void Save(object obj)
{
Session.Save(obj);
}
...
public void Commit()
{
if (!_transaction.IsActive)
{
throw new InvalidOperationException("Oops! We don't have an active transaction");
}
try
{
_transaction.Commit();
Session.Flush(); // I did this FlushMode.Never was set
}
catch (Exception exception)
{
_transaction.Rollback();
throw;
}
}
I've got 3 classes here:
Page, Menu, and Link.
public class Page : IEntity<int>
{
public virtual int Id { get; set; }
public virtual IList<Menu> Menus { get; set; }
}
public class Menu : IEntity<int>
{
public virtual int Id { get; set; }
public virtual IList<Link> Links { get; set; }
}
public class Link : IEntity<int>
{
public virtual int Id { get; set; }
public virtual DateTime CreatedDate { get; set; }
public virtual string Url { get; set; }
}
Then I also have a Mappings:
public class PageMap : ClassMap<Page>
{
public PageMap()
{
Id(x => x.Id).GeneratedBy.Native();
HasManyToMany(x => x.Menus)
.Table("MenuToPage")
.ParentKeyColumn("FkPageId")
.ChildKeyColumn("FkMenuId").Cascade.SaveUpdate(); // the cascade is new here just trying to see if it helps
}
}
public class MenuMap : ClassMap<Menu>
{
public MenuMap()
{
Id(x => x.Id); // I had .GeneratedBy.Native(); attached here too.
HasManyToMany(x => x.Links)
.Table("MenuToLinks")
.ChildKeyColumn("FkLinksId")
.ParentKeyColumn("FkMenuId")
.OrderBy("MenuOrder ASC")
.Not.LazyLoad()
.Cascade.None(); // the cascade is new here just trying to see if it helps
}
}
public class LinkMap : ClassMap<Link>
{
public LinkMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.Url);
Map(x => x.CreatedDate);
Map(x => x.ModifiedDate);
References(x => x.MetaData, "FkMetaDataId").Not.Nullable().Not.LazyLoad();
}
}
Can anyone help me or point me in a direction, I'd really appreciate your help.
Like always thank you,
Kelly
Unfortunately you have posted everything but the construction of your objects before you safe them.
Usually this error can occur if you assign the same collection of entities to different instances. For example (pseudo code)
var menuList = new List<Menu>();...
pageA.Menus = menuList;
pageB.Menus = menuList;
This would set the reference of menuList to both, pageA.Menus and pageB.Menus.
Instead, assign all items of menuList to each page with pageA.AddRange(menuList) or a loop or whatever...

SubSonic, SimpleRepository and entity interfaces

Firstly, I want to apologize for my English, not my strongest side.
To the question. In my current project, I have interfaces to my entities so I can use Subsonic attributes at my head entites and I want to be able to seamlessly switch O/R mapper in the future.
Anyway, I get an error when I try to use my interfaces and SimpleRepositorys classes like Single<>, All<> and so on.
I know why I get the error message but I need help to find a way to get around it.
Error message:
System.InvalidCastException: Unable to cast object of type 'SubSonic.DomainObjects.User' to type 'Core.DomainObjects.IUser'.
Code:
public IUser FindById(int id) {
var user = _repository.Single<User>(x => x.Id == id);
return (IUser)user;
}
As you can see I have tried to make User to IUser order to work when I want to add data, but without success. What can I do to make this work?
Thank you,
Timmie
I don't think subsonic is the problem in this situation. This code will work:
namespace Core.Objects
{
public interface ICustomer
{
int CustomerID { get; set; }
string Name { get; set; }
}
}
Code for the actual class:
namespace Business.Entities
{
public class Customer: Core.Objects.ICustomer
{
public int CustomerID { get; set; }
[SubSonicStringLength(50)]
public string Name { get; set; }
}
}
And finally, the function to get the customer:
private static ICustomer CustomerByID(int id)
{
var repos = new SimpleRepository("Test", SimpleRepositoryOptions.None);
var customer = repos.Single<Customer>(c => c.CustomerID == id);
return (ICustomer) customer;
}

Resources