Collate two collections using Automapper - c#

I am looking around for a tool to automate the collation of two collections into each other and I think Automapper should work for this. We have many instances of this operation and I would like to centralize this logic into a single area.
I have the following two classes:
public class Product
{
public IEnumerable<Order> CurrentCustomerOrders { get;set; }
}
public class Order
{
order properties
}
And they are retrieved via the following calls:
_repo.GetTable<Product>();
_repo.GetTable<Order>().Where(n => n.CustomerId = _customerId);
What I want is to put all the Orders into the Products or something like this:
Mapper.CreateMap<IEnumerable<Order>, IEnumerable<Product>>()
.ForEachMember(n => n.CurrentCustomerOrders), opt => opt.MapFrom(p => p.Where(Order.ProductId == Product.ProductId))
How would I go about doing this using Automapper? Or do you know of a better tool to do this?
Thanks!

Usually you would do the filtering first using Linq or something and then do the mapping. Automapper is just a mapping tool.
e.g.
Product.CurrentCustomerOrders =
Mapper.Map<OrderDO, Order>(Orders.Where(o => o.ProductId == Product.ProductId));

Related

LINQ Query optimalisation using EF6

I'm trying my hand at LINQ for the first time and just wanted to post a small question to make sure if this was the best way to go about it. I want a list of every value in a table. So far this is what I have, and it works, but is this the best way to go about collecting everything in a LINQ friendly way?
public static List<Table1> GetAllDatainTable()
{
List<Table1> Alldata = new List<Table1>();
using (var context = new EFContext())
{
Alldata = context.Tablename.ToList();
}
return Alldata;
}
For simple entities, that is an entity that has no references to other entities (navigation properties) your approach is essentially fine. It can be condensed down to:
public static List<Table1> GetAllDatainTable()
{
using (var context = new EFContext())
{
return context.Table1s.ToList();
}
}
However, in most real-world scenarios you are going to want to leverage things like navigation properties for the relationships between entities. I.e. an Order references a Customer with Address details, and contains OrderLines which each reference a Product, etc. Returning entities this way becomes problematic because any code that accepts the entities returned by a method like this should be getting either complete, or completable entities.
For instance if I have a method that returns an order, and I have various code that uses that order information: Some of that code might try to get info about the order's customer, other code might be interested in the products. EF supports lazy loading so that related data can be pulled if, and when needed, however that only works within the lifespan of the DbContext. A method like this disposes the DbContext so Lazy Loading is off the cards.
One option is to eager load everything:
using (var context = new EFContext())
{
var order = context.Orders
.Include(o => o.Customer)
.ThenInclude(c => c.Addresses)
.Include(o => o.OrderLines)
.ThenInclude(ol => ol.Product)
.Single(o => o.OrderId == orderId);
return order;
}
However, there are two drawbacks to this approach. Firstly, it means loading considerably more data every time we fetch an order. The consuming code may not care about the customer or order lines, but we've loaded it all anyways. Secondly, as systems evolve, new relationships may be introduced that older code won't necessarily be noticed to be updated to include leading to potential NullReferenceExceptions, bugs, or performance issues when more and more related data gets included. The view or whatever is initially consuming this entity may not expect to reference these new relationships, but once you start passing around entities to views, from views, and to other methods, any code accepting an entity should expect to rely on the fact that the entity is complete or can be made complete. It can be a nightmare to have an Order potentially loaded in various levels of "completeness" and code handling whether data is loaded or not. As a general recommendation, I advise not to pass entities around outside of the scope of the DbContext that loaded them.
The better solution is to leverage projection to populate view models from the entities suited to your code's consumption. WPF often utilizes the MVVM pattern, so this means using EF's Select method or Automapper's ProjectTo method to populate view models based each of your consumer's needs. When your code is working with ViewModels containing the data views and such need, then loading and populating entities as needed this allows you to produce far more efficient (fast) and resilient queries to get data out.
If I have a view that lists orders with a created date, customer name, and list of products /w quantities we define a view model for the view:
[Serializable]
public class OrderSummary
{
public int OrderId { get; set; }
public string OrderNumber { get; set; }
public DateTime CreatedAt { get; set; }
public string CustomerName { get; set; }
public ICollection<OrderLineSummary> OrderLines { get; set; } = new List<OrderLineSummary>();
}
[Serializable]
public class OrderLineSummary
{
public int OrderLineId { get; set; }
public int ProductId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}
then project the view models in the Linq query:
using (var context = new EFContext())
{
var orders = context.Orders
// add filters & such /w Where() / OrderBy() etc.
.Select(o => new OrderSummary
{
OrderId = o.OrderId,
OrderNumber = o.OrderNumber,
CreatedAt = o.CreatedAt,
CustomerName = o.Customer.Name,
OrderLines = o.OrderLines.Select( ol => new OrderLineSummary
{
OrderLineId = ol.OrderLineId,
ProductId = ol.Product.ProductId,
ProductName = ol.Product.Name,
Quantity = ol.Quantity
}).ToList()
}).ToList();
return orders;
}
Note that we don't need to worry about eager loading related entities, and if later down the road an order or customer or such gains new relationships, the above query will continue to work, only being updated if the new relationship information is useful for the view(s) it serves. It can compose a faster, less memory intensive query fetching fewer fields to be passed over the wire from the database to the application, and indexes can be employed to tune this even further for high-use queries.
Update:
Additional performance tips: Generally avoid methods like GetAll*() as a lowest common denominator method. Far too many performance issues I come across with methods like this are in the form of:
var ordersToShip = GetAllOrders()
.Where(o => o.OrderStatus == OrderStatus.Pending)
.ToList();
foreach(order in ordersToShip)
{
// do something that only needs order.OrderId.
}
Where GetAllOrders() returns List<Order> or IEnumerable<Order>. Sometimes there is code like GetAllOrders().Count() > 0 or such.
Code like this is extremely inefficient because GetAllOrders() fetches *all records from the database, only to load them into memory in the application to later be filtered down or counted etc.
If you're following a path to abstract away the EF DbContext and entities into a service / repository through methods then you should ensure that the service exposes methods to produce efficient queries, or forgo the abstraction and leverage the DbContext directly where data is needed.
var orderIdsToShip = context.Orders
.Where(o => o.OrderStatus == OrderStatus.Pending)
.Select(o => o.OrderId)
.ToList();
var customerOrderCount = context.Customer
.Where(c => c.CustomerId == customerId)
.Select(c => c.Orders.Count())
.Single();
EF is extremely powerful and when selected to service your application should be embraced as part of the application to give the maximum benefit. I recommend avoiding coding to abstract it away purely for the sake of abstraction unless you are looking to employ unit testing to isolate the dependency on data with mocks. In this case I recommend leveraging a unit of work wrapper for the DbContext and the Repository pattern leveraging IQueryable to make isolating business logic simple.

Loading list of data which references related data in another table

SOLVED
I'm trying to figure out how I can retrieve a collection of related data from a table in entity framework, but I haven't solved it yet.
It is a many-to-many relationship between the two tables as shown in the image below:
My problem is that I want to retrieve the related BOOKs to an Author which is a ICollection of the BOOK table but it gives me an empty list instead when I do the return.
I have tried solving this using explicit loading:
public AUTHOR Read(int Aid)
{
using(var db = new LibraryDBEntities1())
{
var author = db.AUTHORs.Find(Aid);
db.Entry(author).Collection(a => a.BOOKs).Load();
return author;
}
}
I have also tried sloving it using eager loading
return db.AUTHORs.Where(a => a.Aid == Aid).Include("BOOKs").FirstOrDefault();
but it didn't work either. I tried to follow the documentation on microsoft docs Loading Related Entities but I have not been able to figure out what it is I am doing wrong.
Can somebody please help me with this problem and maybe explain where I do wrong?
EDIT 1
This is what the OnModelCreating looks like. Should I change/add code here? I have been using a Database First approach.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
EDIT 2
The database junction should look like this:
EDIT 3
I've forgotten to mention that I'am using a 3-layered architecture including a presentation layer(MVC), a business logic layer and a data access layer. And also a data layer which includes the database, as seen in the image below.
SOLUTION
So i finally solved it! So the problem was not really the problem that I first thought rather it had to du with how I use my Mapper.
I had to add the following code i my model to get it to work:
authorObj.BooksList = Mapper.Map<List<BOOK>, List<Book>>(aauthor.BOOKs.ToList());
Now it works as it should!
EF 6 includes default conventions for many-to-many relationships. You need to include a collection navigation property at both ends. So your model class definitions are absolutely fine. What you should do is updating OnMOdalCreating like below;
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
Then you can use it like below;
return db.AUTHORs.Where(a => a.Aid == Aid).Include(a => a.BOOKs).FirstOrDefault();
P.S: I strongly recommend you to create a model class for the junction type.
EDIT
With Database first approach you have to basically create a model class for each table in the database.
So don't change your OnMOdalCreating method. Add a new Class like below;
[Table("BOOK_AUTHOR")]
public class BookAuthor
{
public int ISBN { get; set; }
public int AId { get; set; }
public virtual Book Book { get; set; }
public virtual Author Author { get; set; }
}
then you need to bind the properties to primary key properties via attributes or FLuent API. After that, you will be able to get an author with books like below
var authorWithBooks = context.Authors
.Include(a => a.BookAuthors.Select(ba => ba.Book))
.Where(...)
.ToList();
Since, its database first approach, please try adding below mapping to your OnModelCreating function
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BOOKs>()
.HasMany<AUTHORs>(b => b.AUTHORs)
.WithMany(a => a.BOOKs)
.Map(m => m.ToTable("BOOK_AUTHOR")
.MapLeftKey("ISBN")
.MapRightKey("Aid"));
// or like below
/* modelBuilder.Entity<BOOKs>()
.HasMany<AUTHORs>(s => s.AUTHORs)
.WithMany(c => c.BOOKs)
.Map(cs =>
{
cs.MapLeftKey("ISBN");
cs.MapRightKey("Aid");
cs.ToTable("BOOK_AUTHOR"); // Use the correct junction table name here. and correct key names
});*/
}
Also, remove the statement that throws exception.
And then use:
db.AUTHORs.Include("BOOKs").Where(a => a.Aid == Aid).FirstOrDefault();
or
db.AUTHORs.Include(a => a.BOOKs).Where(a => a.Aid == Aid).FirstOrDefault();
or simply, directly have predicate in FirstOrDefault without WHERE clause.
db.AUTHORs.Include(a => a.BOOKs).FirstOrDefault(a => a.Aid == Aid); // Same as above two queries.
UPDATE:
var query = db.AUTHORs.Include(a => a.BOOKs).Where(a => a.Aid == Aid);
var firstBook = query.FirstOrDefault(); // Have a break point here. Please see what is in query via quick watch.
In quick watch for query (not firstBook), you should see Sql as shown below:
Please take that SQL statement and post your observations here after executing directly in SSMS like
do you see a join to BOOKs in the query or not
and does the result set has columns from BOOKs.

How to map empty strings as null with automapper?

I need to map empty strings from source model as null to destination model.
At first I used next profile for this:
public class MyProfile:Profile
{
public MyProfile()
{
CreateMap<SrcModel, DestModel>()
.ForMember(dst => dst.Field1, opt =>
{
opt.Condition(src => !string.IsNullOrEmpty(src.src_Field1));
opt.MapFrom(src => src.src_Field1)
})
//.......
//same for other 15 fields
}
}
But duplicating same logic looks not very good and it's hard to modify it.
Also I have tried to create special map for string like this:
CreateMap<string, string>().ConvertUsing(src => string.IsNullOrEmpty(src) ?
null : src)
But such string map has impact on all my maps, but I need such logic only for several maps, not for all.
I also have tried to use ForAllMembers method:
... .ForAllMembers(opt => opt.Condition();
But there is no way do define type of source member, to cpecify some condition for strings.
What is the best way to define some common mapping logic for several members of same type for one map?
Just duplicate the logic, the most I'd do is extract the Condition part into an extension method you can call.

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