Autoproject several sequences into the main ViewModel via Automapper - c#

I'm trying to auto project from SQL server with Automapper some data into my view models.
The view model I have is:
public sealed class WorkstationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string OccupiedByName { get; set; }
}
And the code I'm trying to use is:
Mapper.CreateMap<Workstation, WorkstationViewModel>()
.ForMember(T => T.OccupiedByName, T => T.MapFrom(W =>
W.Sessions.AsQueryable().Select(E => E.StaffMember.Name).SingleOrDefault()));
Two properties Id and Name are auto-projected as they have equal names in Workstation class.
The exception I get on some codelines like this
var model = WorkstationsRepository.GetAll().Project()
.To<WorkstationViewModel>().SingleOrDefault();
is some weird object reference is null exception and on the top of the stack trace there are some automapper's CreateExpression<> methods which gives me a conclusion that the automapper cannot generate a good one expression to translate it to SQL code.
When I use simple maps, like .Name to .Category.Name or other one-item retrievals from the SQL table, it works perfectly. All I need is to get access to multiple items while projecting the sequence via Automapper.

The newer Project().To() API takes a totally different route than the "classic" Mapper.Map() API. I think that the latter would run in your case, but of course you won't have the benefit of the projection trickling thought to the SQL.
During Project().To() AutoMapper tries to get MemberInfos (reflection) from the involved types, which it uses to create lambda expressions. This means that the source properties in a mapping must be members of the source type. Evidently, W.Sessions.AsQueryable().Select(... is not a member of Workstation. So somewhere along the line AutoMapper bumps into a null memberinfo.
So, Project().To() is a bit restricted. In this particular case a remedy may be to map a Session with its parents WorkStation and StaffMember to the model. Reference navigation properties will map OK with Project().To().

Related

When retrieving a set with EFCore, why do the retrieved entities not include their relationships to other objects?

While using Entity Framework Core with SQL Server I encountered an unexpected problem. I have an entity A that has a one-to-many relationship on entity B.
[Table("client")]
public class Client
{
public long ID { get; set; }
public string Name { get; set; }
public ICollection<Configuration> Configurations { get; set; } = new LinkedList<Configuration>();
}
I get a list of instances of entity A from the database like this:
public ICollection<Client> GetAllClients()
{
return _dbContext.Clients.ToList();
}
When I call this function I get a list of instances without the instances of entity B in the relationship. Why are the objects in the relationship not retrieved correctly?
I've also found out that if I add this line of code to the function the entities are retrieved as expected.
public ICollection<Client> GetAllClients()
{
var test = _dbContext.Configurations.ToList();
return _dbContext.Clients.ToList();
}
This makes no sense to me. What am I missing here?
You can use the Include method to specify related data to be included in query results (Eager loading).
Take a look in the following example:
public ICollection<Client> GetAllClients()
{
return _dbContext.Clients.Include(x => x.Configurations).ToList();
}
You can check the MSDN reference.
Related reference/collection properties must either be eagerly or explictly loaded. You generally want to eagerly load using Include:
var clients = await _context.Clients.Include(x => x.Configurations).ToListAsync();
Alternatively, you can lazy load, but that's generally a bad idea, as it can lead to N+1 query problems (i.e. you issue one query, and then a separate additional query for each item as you iterate through, which is obviously highly inefficient). Regardless, lazy loading requires two things:
The reference/collection property must have the virtual keyword. EF adds lazy loading functionality by creating a dynamic proxy of your entity class and overriding the property getter. Overriding, of course, can only be done on virtuals.
You have to explicitly add the lazy-loading services:
services.AddDbContext<MyContext>(o => o.UseLazyLoadingProxies()
.UseSqlServer(connectionString));
It works when you query the configurations as well, because EF has object-fixup. In other words, it will automatically fill related reference/collection properties, if it has previously retrieved those objects already and has them in its object cache. Otherwise, and if you do not otherwise load the relations, the property will remain null.

Ignore properties dynamically at time of mapping using AutoMapper

I'm using AutoMapper in my .net-core application to create DTOs from my Nhibernate entities to send to the client as a JSON. However, there are some requests that I need to omit certain properties on my entity. For example, assume I have the following classes:
public class Person
{
public ICollection<Friend> Friends { get; set; }
}
public class PersonDTO
{
public ICollection<FriendDTO> Friends { get; set; }
}
Now, there are cases where I need to send to the client a Person with all of their friends, however sometimes I just need the person. I have a automapper configuration that defines a simple mapping between Person and PersonDTO with no omitted properties because that's the most common case. However, rather than create two separate DTOS (one with the Friends property mapped and the other with it ignored), it would be nice to somehow specify this at the time of mapping. I've tried the following, but it doesn't seem to work:
var personDtoWithoutFriends = _mapper.map<Person, PersonDTO>(person,
opts => opts.ConfigureMap()
.ForMember(dest => dest.Friends, act => act.Ignore()))
I know I can solve this problem with multiple DTOs, however this is an extremely common pattern in my application and I would end up creating a large number of DTOs. Ignoring certain properties in the few cases I need to seems like a much more elegant solution. Am I doing something wrong or is this just not possible using AutoMapper?

Project into a class with Private properties using Entity Framework

I'm using Entity Framework with a code-first approach to store data for a C# application I'm working with a SQL Server database. A challenge I'm currently running into involves a structure (approximately) like this:
public class MainEntity
{
// Data
public List<SubEntity> SubEntities { get; private set; }
}
public class SubEntity
{
// More Data
bool DoNotLoad { get; set; }
}
Now, I know that Entity Framework is able to "see" private property setters and populate the entities using reflection. That's why this works:
IEnumerable<MainEntity> Entities = MainEntities.Include(m => m.SubEntities).ToList();
And it will retrieve the MainEntity and all of its SubEntities from the database even though the setter for SubEntities is private.
I also know that Entity Framework supports more free-form projections, like so:
var projectedEntities = MainEntities.Select(m =>
new {
Main = m,
Sub = m.SubEntities.Where(s => !s.DoNotLoad)
}
);
And then I'll have an anonymous type with the main entity and its sub entities, with a filter applied to the sub entities.
However, I would like to combine the two methods and end up with MainEntity objects that have their SubEntity property populated, but filtered.
Unfortunately, this doesn't work:
var invalidEntities = MainEntities.Select(m =>
new MainEntity{
SubEntities = m.SubEntities.Where(s => !s.DoNotLoad)
}
);
C# doesn't let me use property initialization that way because SubEntities has a private setter, even though Entity Framework would work around that. Is there a way to make this work how I want it? My first priority is to avoid making two queries (e.g. get MainEntity, get filtered SubEntities, use specialized code to insert it), but I would also like to actually do the filtering in the database rather than getting everything and then filtering locally (e.g. MainEntity.FilterSubEntities()). Making the setter public isn't entirely impossible, but in order to use Property initialization I think I would need to change EVERY setter to public, which I would rather avoid.
I've been told that this is possible by projecting into an Anonymous type and if I name things in a certain way Entity Framework will "recognize" that it should project into MainEntity instead, but I haven't been able to find any references to this anywhere else. If that is possible then that would be my preferred method since it seems flexible enough to apply in various other situations where I need to filter in other ways.
I've found it
I've finally found an example of what I was told, and my testing indicates that it works. A few notes about the method:
It does not work to arbitrarily project into private properties or properties with private setters. It only allows for more complex filtering of sub-entities, which fortunately was the primary use I was looking for.
This may be undocumented behavior, or abuse of some other feature, so for all I know it could break at any time, and I can't necessarily state what would happen in any edge-cases that arise.
I don't have any evidence that this is better by any metric than simply filtering locally, and it could be significantly worse. I haven't measured it, and I recognize that this seems to be "odd behavior" that might mess with Entity Framework's normal optimizations. I think it's pretty cool though.
With that out of the way, this is the technique using the sample classes described in the question:
var queryResult = MainEntities.Select(m =>
new {
MainEntity = m,
m.SubEntities.Where(s => !s.DoNotLoad)
})
.ToList();
var finalList = queryResult.Select(q => q.MainEntity).ToList();
(Doing this on two lines and using a temporary variable isn't strictly necessary, but I think it clarifies that a DB query is executed at the first ToList() and then additional operations are applied locally.)
I believe that this works because Entity Framework populates navigation properties in a particularly eager manner. Essentially it populates the SubEntities List purely by adding every loaded SubEntity that has a foreign key to that MainEntity, regardless of what caused those entities to be loaded. That is speculation though, all I definitely know is that it currently works how I need it to.

Linq: how return property of specific object

i have the following model:
public partial class location
{
public int Id { get; set; }
public double Lat { get; set; }
public double Long { get; set; }
public virtual ICollection<localserver> localserver { get; set; }
}
When, in a controller, i do:
List<location> l = db.location.ToList();
i get also the localserver object. How, in LINQ, get only the property of location (Id, Lat and Long) without using the Select argument in linq?
The way to extract part of an object is to project it to a new form, which is what .Select() is for:
var result = db.location
.Select(x => new
{
Id = x.Id,
Lat = x.Lat,
Long = x.Long
})
.ToList();
Now, you've specifically asked how to do this without using .Select(), so... really, the answer is "you don't". You've ruled out the tool that's specifically designed for the scenario you're presenting, so no useful answer remains.
To address the intent of your question, however, I'm going to make a guess that you don't want to load the collection of localserver objects, perhaps because it's large and uses a lot of memory. To solve that problem, I would suggest the following:
If you're using Entity Framework Code First, check your cascade options when creating the database (if you're setting any).
Check that the collection is declared as virtual (you've shown it as such here but check the actual source)
Check that you have lazy-loading enabled, by setting myContext.ContextOptions.LazyLoadingEnabled = true; at some point
This should allow the application to lazy-load that property, which means that the contents of that localserver property will only be retrieved from the database and loaded into memory when they're actually needed. If you don't ever access it, it won't be loaded and won't take up any memory.
When you are getting Location list entity is not pulling Localserver object data, the thing that entity framework has feature called lazy loading. Lazy loading means you can get associated object at any time you need, no need write one more separate linq query to get. This will pull data from associated object only when you call them inside the code, then entity framework will make one more call to database to load that associated object data.
So just go with your code, but if you want some selected columns from Location object itself than you have write Select and supply column names.

Protect a list from the undesired Add leaving Entity Framework happy

I have an object, say the usual Order that have a collection of LineItem. To satisfy some business rules I need to have complete control against the list (i.e. nobody can add an element to this list simply using the built in Add() method). So I do something like:
public class Order {
private List<LineItem> lineItems {get; set;}
public IEnumerable<LineItem> LineItems {
get { return lineItems.AsReadOnly(); }
}
}
This solution do the job but have a big side effects if other player come into play (e.g. Entity Framework). In fact during the model building, EF expects an ICollection (while I need an IEnumerable):
modelBuilder.Entity<Order>()
.HasMany<LineItem>(e => e.LineItems) // here is the exception: No implicit conversion among IEnumerable and ICollection
.OtherMappingProperties(); // <-- this don't belong to EF's Fluent API :)
EDIT 1 - The solution provided by #Pieter21 avoid the compile time error, since LineItem is now a Colection, but I get a runtime exception when somewhere in my code I have something like:
return Set.Include(s => s.LineItems)
since none have set lineItems (none could since is private) so I get a NullPointerException.
Since the Entity configuration and Domain model are in different package the use of internal instead of private would not resolve the problem unless the use of InternalsVisibleTo (but I don't want to spoil the AssemblyInfo.cs of the Domain model with something persistence related)
How to workaround this issue?
PS: The EF configurations and domain model are in different namespaces.
Can you do something like:
public ICollection<LineItem> LineItems
{
get
{
return new ReadOnlyCollection<LineItem>(lineItems);
}
}

Categories