AutoMapper > Using Queryable Extensions and some mapping fails - c#

Is there any way to map these EntityFramework entities with AutoMapper?
I'm getting an error when trying to map a DateTime object (from my DBContext entity) to a TimeSpan property on my DTO.
This is the exception
----------- Exception #0 -----------
Type: System.NotSupportedException
Message: The specified type member 'TimeOfDay' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Source: EntityFramework
It used to work when mapping the Entity after ToList() call, but now using AutoMapper's ProjectTo<> IQueryable extension it is obviously trying to convert the expression to an expression that SQL can understand.
My question - is it possible to configure mapping on certain objects to take place after the query has been executed on the server? (e.g. after the ToList() call)
CloseTime property on the DB entity is a DateTime object but we are mapping to a TimeSpan object
For Now - I'm just ignoring the properties like so
cfg.CreateMap<CustomerShipTo, ShipToBase>()
.ForMember(desc => desc.OpenTime, src => src.Ignore())
//.ForMember(desc => desc.OpenTime, src => src.CloseTime.TimeOfDay)
.ReverseMap();

.ProjectTo() needs to be able to ultimately map down to SQL so there are limitations to what the mappings can do as compared to using .Map(). A work-around for issues like this is to map a "raw" data value then use a translation property in the DTO. In your case since this is a data type conversion you could call the raw value "OpenDateTime", normally if I want to use the same name I will use a prefix of "Raw" to reflect the raw DB value. (I.e. RawOpenTime)
[Serializable]
public class ShipToBase
{
// ...
public DateTime OpenDateTime { get; set; }
public Timespan OpenTime
{
get { OpenDateTime.TimeOfDay; }
set { OpenDateTime = DateTime.MinValue().Add(value); } // Setter if needed.
}
}
Then in the mappings:
cfg.CreateMap<CustomerShipTo, ShipToBase>()
.ForMember(desc => desc.OpenDateTime, src => src.OpenTime)
.ForMember(desc => desc.OpenTime, src => src.Ignore())
.ReverseMap();

Related

C# EFCore HasConversion to an object But EF Recognize Object Like an Entity

I have a question about HasConversion in EFCore.
I decided to save in DB a model like a json String then I decided to implement automatic conversion of this Object with .HasConversion method in Configure Method of EFCore.
I have use this method in with Enum-> String and all run perfect but using this approach with object the situation become more complex:
I simplify scenario:
public class ObjectA
{
public string A {get; set;}
}
public partial class EntityA
{
public ObjectA objectA {get; set;} //this is my json object in DB
}
So I have a column in DB nvchar that i want to converto to ObjectA when I extract from DB.
As EF core documentation say, i implemented a conversion class
public void Configure(EntityTypeBuilder<EntityA> builder)
{
builder.Property(x => x.ObjectA)
.HasConversion(y => JsonSerializer.Serialize(
y,
new JsonSerializerOptions()
),
y => JsonSerializer.Deserialize<ItemList>(
y,
new JsonSerializerOptions()
)
);
}
and when this mapper run all seams ok. But i use it in query for redeem object and convert it i have this error.
The entity type 'ObjectA' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'.
but ObjectA is not an Entity/Table so where is the problem?
I have same error even if i try to add-Merge theoretically EF should read conversion and it shouldn't automatically see the AbjectA as an entity but simply a string.
Sorry for my english i hope is all clear.
Tanx in advice
Sorry, after returning the project and fixing the code the error would show up again. I then understood that the error was due to the fact that I had added the same ObjectA object in a new model but I had not inserted the Mapper in the IEntityTypeConfiguration.
So my code is correct.
Thank you all

EF Core fluent mapping to inner object properties

I have a class that contains some properties.
For some architectural reasons, I have an instance of another objet into my class.
Simple example
public class MyEntity {
public MySubEntity SubEntity {get; set;}
}
For this, I create fluent mapping like :
builder.ToTable(MyEntity.CONST_TABLE_NAME);
builder.HasKey(m => m.Id);
builder.Property(m => m.Column1).IsRequired();
builder.Property(m => m.SubEntity.Column2).IsRequired();
I cannot integrate all my subEntity properties into my main entity (my subEntity has its own intelligence). I just want to map my subentity properties, which is NOT stored in a separated table, to myEntity table.
The last line throw an exception :
The expression 'm => m.SubEntity.Column2' is not a valid property expression. The expression should represent a property access: 't => t.MyProperty'.
How can I perform such mapping ?
EF Core doesn't support this type of mapping for now. It will not be supported in EF Core 1.0 RTM (see my github issue : https://github.com/aspnet/Home/issues/1330)
As I described in my github issue, I figured out 2 solutions :
1) Create a derived class from my model, specialy designed for EF, and expose all properties as simple. It will need more mapping when insert/update and retrieve from Db. We don't choose this option
2) Create proxy properties. In my example, this is like :
public class MyEntity {
private MySubEntity SubEntity {get; set;}
public string SubEntityValue
{
get
{
return SubEntity.Value;
}
set
{
SubEntity.Value = value;
}
}
This seems to be the best solution (we choose this one).

The specified type member is not supported in LINQ to Entities. entity members, and entity navigation

public partial class User : IUser
{
public long ID {get; set;}
public BaseUser BaseUser
{
get
{
var context = new Factory().Create<ContextDB>();
return context.Users.Find(this.ID);
}
}
}
and
var result = _Context.Employees.Where(t => t.User.BaseUser.UserName.ToLower().Trim().Contains(searchKey));
Here I am getting an exception:
The specified type member 'BaseUser' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Any solution to this?
You have written Linq query. And you are working with Entity Framework. So, you are using Linq-To-EntityFramework.
.Net will translate your Linq query to database query based on the type of the RDMS which you are using.
For example, let's take this code:
context.Users.Select(x => x.Id == 5);
This will be translated to:
select * from User where Id=5;
So, it means that .net will throw exception if it couldn't translate it to the database query. For example, your query. You have created property in your class. And you are beleiving that it will be translated to the database query? How? There is no way! This is the reason of the exception.
Also, your BaseUser property seems unusual to me. BaseUser will be same with User if you have configured everything right.

AutoMapper objects with different property types

I want to map my Entity Framework entities (generated from a legacy database) to custom DTO objects (which should be nice and clean).
My legacy DB has entities looking a bit like this:
internal class Order {
int id;
string Shipping_date;
string quantity;
}
And I want to map it to a nicer DTO object:
public class OrderDto {
int id;
DateTime? ShippingDate;
int Quantity;
}
I have written an "entity container" to provide dependency injection, which returns values this way:
public IEnumerable<OrderDto> GetPaginatedOrders(int page, int pageSize)
{
return this.db.Orders
.OrderByDescending(c => c.id)
.Paginate(page, pageSize)
.Project()
.To<OrderDto>()
.AsEnumerable();
}
So: change of types, and change of property names.
Were it only change of property names, it would be easy-but-tedious:
Mapper.CreateMap<Order, OrderDto>()
.ForMember(dest => dest.Quantity, opt => opt.MapFrom(src => src.quantity))
.ForMember(dest => dest.ShippingDate, opt => opt.MapFrom(src => src.Shipping_date));
This is not enough with type changes. I tried a whole bunch of stuff:
Parsing the properties at the mapping declaration, like src => int.Parse(src.quantity) but Linq doesn't like it.
Extending the EF entities with custom properties like QuantityInt { get { return int.Parse(this.quantity) } } and using these in the mapping, but AutoMapper doesn't like it, and explicitly don't support them.
Mapping system types one to another like Mapper.CreateMap<string, int>().ConvertUsing(Convert.ToInt32) but I still get Unable to create a map expression from System.String to System.Int32 errors.
Using custom converters for my class, but I always get empty values from ResolutionContext.SourceValues at run-time from my entities (I'm guessing that they are disposed before AutoMapper gets them or something like this).
I'm realizing that AutoMapper is convention-based, so maybe I should use another tool, but which one exist?
Thanks for your help!
.Project() uses Linq to entities, which generates SQL and naturally only understands a very limited set of functions.
If you use
Mapper.Map<IEnumerable<Order>, IEnumerable<OrderDto>>(src)
your conversions will work fine.

Entity Framework + AutoMapper ( Entity to DTO and DTO to Entity )

I've got some problems using EF with AutoMapper. =/
for example :
I've got 2 related entities ( Customers and Orders )
and they're DTO classes :
class CustomerDTO
{
public string CustomerID {get;set;}
public string CustomerName {get;set;}
public IList< OrderDTO > Orders {get;set;}
}
class OrderDTO
{
public string OrderID {get;set;}
public string OrderDetails {get;set;}
public CustomerDTO Customers {get;set;}
}
//when mapping Entity to DTO the code works
Customers cust = getCustomer(id);
Mapper.CreateMap< Customers, CustomerDTO >();
Mapper.CreateMap< Orders, OrderDTO >();
CustomerDTO custDTO = Mapper.Map(cust);
//but when i try to map back from DTO to Entity it fails with AutoMapperMappingException.
Mapper.Reset();
Mapper.CreateMap< CustomerDTO , Customers >();
Mapper.CreateMap< OrderDTO , Orders >();
Customers customerModel = Mapper.Map< CustomerDTO ,Customers >(custDTO); // exception is thrown here
Am I doing something wrong?
Thanks in Advance !
The problem I had was related to updates to EntityCollection references. AutoMapper creates a new instance of the relation when mapping from the DTO to the Entity, and that doesn't please the EF.
What solved my problem was configuring AutoMapper to use the destination value for my EntityCollection properties. In your case:
Mapper.CreateMap< CustomerDTO , Customers >().ForMember(c => c.Orders, o => o.UseDestinationValue());
That way AM will not create a new EntityCollection instance, and will use that wich came with the original Customer entity.
I'm still working for a way to automate this, but for now it solves my problem.
Try mapping to an existing object:
entity = Mapper.Map<MyDTO, NyEntity>(dto, entity);
And keep the Ignore()'s in place.
http://groups.google.com/group/automapper-users/browse_thread/thread/24a90f22323a27bc?fwc=1&pli=1
Your problem is because Automapper loses the EntityKey associated with the record. As the EntityFramework does not by default handle POCO's (Plain Old CLR Object)
Jay Zimmerman has a good example here of how to handle this from is. gd /4NIcj
Also from Jaroslaw Kowalski (part of the EF team I believe ) has this example for using POCO's within EF, which may translate well to use with Automapper (I've not yet had a chance to try it) : http://blogs.msdn.com/jkowalski/archive/2008/09/09/persistence-ignorance-poco-adapter-for-entity-framework-v1.aspx
I'm not sure what your problem is, but - when i wanted to use LINQToEntities (switched to NHibernate),
i managed to use automapper with success.
Take a look at code:
public class SimpleMapper<TFrom, TTo>
{
public static TTo Map(TFrom fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<TFrom, TTo>(fromModel);
}
public static IList<TTo> MapList(IList<TFrom> fromModel)
{
Mapper.CreateMap<TFrom, TTo>();
return Mapper.Map<IList<TFrom>, IList<TTo>>(fromModel);
}
}
public class RepositoryBase<TModel, TLINQModel>
{
public IList<TModel> Map<TCustom>(IList<TCustom> model)
{
return SimpleMapper<TCustom, TModel>.MapList(model);
}
public TModel Map(TLINQModel model)
{
return SimpleMapper<TLINQModel, TModel>.Map(model);
}
public TLINQModel Map(TModel model)
{
return SimpleMapper<TModel, TLINQModel>.Map(model);
}
public IList<TModel> Map(IList<TLINQModel> model)
{
return SimpleMapper<TLINQModel, TModel>.MapList(model);
}
public IList<TLINQModel> Map(IList<TModel> model)
{
return SimpleMapper<TModel, TLINQModel>.MapList(model);
}
}
It's quite cryptic, always recreates mappings, but it worked. I hope it helps somehow. :)
Now, with new version of AutoMapper, the recommended way is using Queryable-Extensions:
When using an ORM such as NHibernate or Entity Framework with
AutoMapper's standard Mapper.Map functions, you may notice that the
ORM will query all the fields of all the objects within a graph when
AutoMapper is attempting to map the results to a destination type.
If your ORM exposes IQueryables, you can use AutoMapper's
QueryableExtensions helper methods to address this key pain.
The .ProjectTo() will tell AutoMapper's mapping engine
to emit a select clause to the IQueryable that will inform entity
framework that it only needs to query the Name column of the Item
table, same as if you manually projected your IQueryable to an
OrderLineDTO with a Select clause.
Create a mapping:
Mapper.CreateMap<Customer, CustomerDto>();
And project query to dto:
var customerDto =
session.Query<Customer>().Where(customer => customer.Id == id)
.Project().To<CustomerDto>()
.Single();
AutoMapper is very expressive when it comes to mapping error. read the exception message carefully.
another important thing is to remember to call Mapper.AssertConfigurationIsValid(); after creating the mappings. it gives an error if the mapping is wrong, thus preventing an exception later in the application runtime.
You should ignore mapping of some entity properties like so:
Mapper.CreateMap<CustomerDto, Customer>()
.ForMember(dest => dest.EntityKey, opt => opt.Ignore())
.ForMember(dest => dest.Licenses, opt => opt.Ignore())
.ForMember(dest => dest.AccessCodes, opt => opt.Ignore());
If you examine the message from the exception thrown by Automapper, you should see the entity properties that cannot be mapped and ignore them as above.
As you can read here you need to do the following
You can update entities with AutoMapper. Here's how: pass both the DTO and the entity object to AutoMapper's Map method. That's what this code does:
custExisting = Mapper.Map(Of CustomerDTO, Customer)(custDTO, custExisting)
Also beware of mapping issues like the one described here
These tips worked for me.

Categories

Resources