Nhibernate Projection Query DTO, use method in stead of property - c#

This works:
projections.Add(Projections.Property(Member<MailOrder>.From(x => x.AssigneeCode).QualifiedPath), Member<MailOrderItem>.From(x => x.AssigneeCode).Path);
projections.Add(Projections.Property(Member<MailOrder>.From(x => x.AssigneeName).QualifiedPath), Member<MailOrderItem>.From(x => x.AssigneeName).Path);
projections.Add(Projections.Property(Member<MailOrder>.From(x => x.AssigneeType).QualifiedPath), Member<MailOrderItem>.From(x => x.AssigneeType).Path);
This does not off course
projections.Add(Projections.Property(Member<IMailOrderAssignee>.From(x => x.AssigneeCode).QualifiedPath), Member<MailOrderItem>.From(x => x.Code).Path);
projections.Add(Projections.Property(Member<IMailOrderAssignee>.From(x => x.AssigneeName).QualifiedPath), Member<MailOrderItem>.From(x => x.GetName()).Path);
projections.Add(Projections.Property(Member<IMailOrderAssignee>.From(x => x.AssigneeType).QualifiedPath), Member<MailOrderItem>.From(x => x.GetType()).Path);
This does not work because of two things:
there is no persister for theinterface
Methods are used in Property way.
I've searched a lot in the world of Nhiberante, but it seems to me that this is quite difficult.
The IMailOrderAssignee is an interface for two rootentity's (let's call them RootX and RootY). In the context of my MailOrders, it isn't important what root it is, as long as I have a reference to it + The Name and it's code and emailaddresses.
The IMailOrderAssignee is mapped with the any - tag in the mapping file. (that works brilliant, but I could do it with an discriminator also).
My question:
Is it possible to use the result
of a method in a projection query so
the result is in the DTO?
Is it possible to uses contracts
in projection querys (I guess
not...)

Why not do the projection in memory?
Example:
var criteria = someCriteriaThatReturnsPersistentEntities;
var items = criteria.List<IMailOrderAssignee>();
var projected = items.Select(i => new
{
Prop1 = i.SomeMethod(),
Etc
});

Related

Linq one to many with filter

I have an Entity Framework database that I'm querying, so I'm using linq-to-entities.
Here's my query:
// 'Find' is just a wrapper method that returns IQueryable
var q = r.Find(topic =>
topic.PageId != null &&
!topic.Page.IsDeleted &&
topic.Page.IsActive)
// These are standard EF extension methods, which are used to include
linked tables. Note: Page_Topic has a one-to-many relationship with topic.
.Include(topic => topic.Page.Route)
.Include(topic => topic.Page_Topic.Select(pt => pt.Page.Route))
// HERE'S THE QUESTION: This select statement needs to flatten Page_Topic (which it does). But it seems to do it in the wrong place. To explain, if I were to include another column that depended on Page_Topic (for example: 'PillarRoutName2', I'd have to apply the same flattening logic to that column too. Surely the filtering of Page_Topic should be done higher up the query in a DRY way.
.Select(x => new
{
TopicName = x.Name,
HubRouteName = x.Page.Route.Name,
PillarRouteName = x.Page_Topic.FirstOrDefault(y => y.IsPrimary).Page.Route.Name
}).ToList();
Surely the filtering of Page_Topic should be done higher up the query in a DRY way.
Correct! And it's easy to do this:
.Select(x => new
{
TopicName = x.Name,
HubRouteName = x.Page.Route.Name,
FirstTopic = x.Page_Topic.FirstOrDefault(y => y.IsPrimary)
})
.Select(x => new
{
TopicName = x.TopicName,
HubRouteName = x.HubRouteName,
PillarRouteName = x.FirstTopic.Page.Route.Name,
PillarRoutName2 = x.FirstTopic. ...
}).ToList();
Depending on where you start to get properties from FirstTopic you can also use x.Page_Topic.FirstOrDefault(y => y.IsPrimary).Page or .Page.Route in the first part.
Note that you don't need the Includes. They will be ignored because the query is a projection (Select(x => new ...).

AutoMapper - Map two source collections of different types into a single destination collection

I have two collections on a source object of different types. I want to map both collections (a union of the two) to a single destination collection of a type that has all the members from both the source types. If I do this:
CreateMap<Company, CompanyResponse>()
.ForMember(x => x.Owners, m => m.MapFrom(x => x.BusinessOwners))
.ForMember(x => x.Owners, m => m.MapFrom(x => x.IndividualOwners));
It only maps the last mapping. I tried a more elaborate mapping but this appeared to break the entity framework projection integrations that automapper does. I am using ProjectTo.
This is what I tried which also does a good job of conveying the result I want.
CreateMap<Company, CompanyResponse>()
.ForMember(x => x.Owners, m => m.ResolveUsing(x => x.BusinessOwners.Select(o => new OwnerResponse
{
Type = UpdateRegistrationCommand.CompanyUpdate.OwnerType.Business,
Address = o.Address,
PercentageShareholding = o.Percentage,
BusinessName = o.Name,
BusinessNumber = o.Number
})
.Union(x.IndividualOwners.Select(o => new OwnerResponse
{
Type = UpdateRegistrationCommand.CompanyUpdate.OwnerType.Individual,
Address = o.Address,
PercentageShareholding = o.Percentage,
Title = o.Title,
FirstName = o.FirstName,
MiddleNames = o.MiddleNames,
LastName = o.LastName
}))));
Has anyone done something like this?
According to AutoMapper's documentation, you might want to try ProjectUsing<>() instead of ResolveUsing<>().

Linq select returning string instead of object

I have the following code:
var languages = _languageService
.GetAll()
.Select(x => (((LanguageViewModel) new LanguageViewModel().InjectFrom(x))))
.ToList();
When executing this, languages becomes, as expected, a collection of LanguageViewModel objects:
What I am trying to do is, when selecting, also convert the object's Code property to uppercase, as so:
var languages = _languageService
.GetAll()
.Select(x => (((LanguageViewModel) new LanguageViewModel().InjectFrom(x)).Code = x.Code.ToUpper()))
.ToList();
I'm expecting the languages object to have multiple LanguageViewModels in it but it looks like this:
My guess is the fact that I'm using a statement like Select(x => (new Object().Property = Value)) it selects the Property. But then, how can I return an object with one of its properties changed? Using object initializer before inject is not an option as it gets overriden, using it after the Inject is not possible, as it is not casted yet, so I got to the solution here which does not seem to work. Any advice greatly appreciated.
You can't write the lambda body as a single expression that does what you want, but you don't need to. You can put multiple statements in a lambda:
var languages = _languageService
.GetAll()
.Select(x => {
var lvm = (LanguageViewModel)new LanguageViewModel().InjectFrom(x);
lvm.Code = x.Code.ToUpper();
return lvm;
})
.ToList();
Your Select line could be rewritten to
.Select(x =>
{
var vm = new LanguageViewModel().InjectFrom(x);
vm.Code = vm.Code.ToUpper();
return vm;
})

Entity Framework include with a condition

I have two entities, which are related, ActiveContract and BudgetSource. I'm trying to grab all the BudgetSource which are marked as isActive = true, along with all associated ActiveContracts which are also marked isActive = true. I've tired:
var d = budgetSourceRep.All.Where(x => x.isAcitve)
.OrderBy(x => x.SourceName)
.Include(z => z.ActiveContracts.Where(q => q.isActive))
.Select(y => new EditSelectItemViewModel
{
Id = y.Id,
SourceName = y.SourceName,
DisplayOnNew = y.DisplayOnNew,
NumberOfTimesUsed = y.ActiveContracts.Count()
}).ToList();
but that gives me an error
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties. Parameter name: path
I then changed it to put the filter in the projection:
var d = budgetSourceRep.All.Where(x => x.isAcitve)
.OrderBy(x => x.SourceName)
.Include(z => z.ActiveContracts)
.Select(y => new EditSelectItemViewModel
{
Id = y.Id,
SourceName = y.SourceName,
DisplayOnNew = y.DisplayOnNew,
NumberOfTimesUsed = y.ActiveContracts.Count(a => a.isActive)
}).ToList();
that works, but I'm assuming it's going to make a second query to do that? If so, is there a way to do it in one shot.
that works, but i'm assuming it's going to make a second query to do that?
No, it is not. You can see for yourself by looking at the generated SQL for this query.
It is within the bounds of theoretical possibility for the query provider to have successfully translated your first query into SQL, but doing so is...hard. This simply isn't a feature that the developers of EF chose to put into their query provider. Instead you are forced to project out a collection of related entities in some way to filter them, you cannot use Include to do it for you.

Grouping using linq to entity return non correct result

In my recent application, i have a Document entity and this document can refer from a user to another user, also each group of users has a DocumentStation; these refers logged in DocumentStationHistory table:
Now, i want list all last document refers that loges in DocumentStationHistory table to a Dictionary using EF code first(group by documentId).
so i wrote these method:
public Dictionary<int, DocumentStationHistory> GetLastDocumentStationHistoryListOfDocuments(string criteria)
{
Dictionary<int, DocumentStationHistory> result = new Dictionary<int, DocumentStationHistory>();
using (IUnitOfWork uow = new MyContext())
{
DocumentStationHistoryRepository repository = new DocumentStationHistoryRepository(uow);
result = repository.All().
Include(x => x.DocumentStation).
Where(criteria,new object[]{}).
OrderBy(d=>d.DocumentId).
OrderBy(d=>d.DocumentStationHistoryId).
GroupBy(g => (int)g.DocumentId).
ToDictionary(g => (int)g.Key, g => g.LastOrDefault());
return result;
}
}
it return a dictionary, but the result isn't correct, it doesn't return the last refer of each document, also the DocumentStation navigation property, in the result is null.
where is my mistake?
Two problems for the ordering:
You're using OrderBy twice, which almost certainly doesn't do what you think it does. You should usually use OrderBy followed by ThenBy
I don't believe GroupBy is guaranteed to maintain the ordering of the rest of the sequence. You should order within the grouping:
result = service.All()
.Include(x => x.DocumentStation)
.Where(criteria, new object[]{})
.GroupBy(g => (int)g.DocumentId)
.ToDictionary(g => (int)g.Key,
g => g.OrderBy(d => d.DocumentId)
.ThenBy(d => d.DocumentStationHistoryId)
.Last());
(There's no need to use LastOrDefault - there has to be at least one element, otherwise there wouldn't be a group.)
An alternative to using Last would be to use OrderByDescending and ThenByDescending, then First, by the way.
I don't know about the DocumentStation inclusion part, I'm afraid.

Categories