Calling an object constructor from NHibernate QueryOver SelectList - c#

I have a DTo class UserDTO with the following constructor public UserDTO(User user). I have also created an NHibernate query which retrieves a IList<TodoDTO> where each TodoDTO has the following property public IList<UserDTO> ResponsibleUsers { get; set; }.
I am wondering if it would be possible to this constructor on UserDTO in my query, so something like this:
var responsibleUsers = session.QueryOver<UserTodo>()
.JoinAlias(ut => ut.User, () => userAlias)
.Where(ut => ut.Todo.Id.IsIn(_todos.Select(t => t.Id).ToArray()))
.Select(u => new UserDTO(userAlias)).ToList<UserDTO>();
The constructor looks like this:
public UserDTO(User user) {}
The problem is that when I run this code the parameter in the UserDTO constructor the user is null.

Calling a Constructor, (or any other code) in the syntax of a query won't work. the entities here are only used to resolve the table and columns for the sql query. The ability to call methods of these objects is missleading...
You can use a Projection to select data from one, or multiple db-entities to a new object (in your case: to the UserDTO)
UserTodo userTodo = null
UserDTO result = null;
var responsibleUsers = session.QueryOver<UserTodo>(() => userTodo)
.JoinAlias(ut => ut.User, () => userAlias)
.Where(ut => ut.Todo.Id.IsIn(_todos.Select(t => t.Id).ToArray()))
.SelectList(list => list
.Select(() => userAlias.FirstName).WithAlias(() => result.FirstName)
.Select(() => userAlias.UserId).WithAlias(() => result.IdOfUser)
.Select(() => userTodo.Age).WithAlias(() => result.Age) // if you require any values from UserTodo
)
.TransformUsing(Transformers.AliasToBean<UserDTO >())
.List<UserDTO >();

Related

Reuse code in AutoMapper ForMember mappings

I'm working on a solution with Entity Framework Core and AutoMapper (version 9).
The mapping from my entity classes to DTOs is done with projections.
var dto = await context.Companies
.ProjectTo<MyDto>(config, new { departmentName = "Sales" });
In the mapping, I select a certain department.
public class MyProfile : Profile
{
public MyProfile()
{
string departmentName = null;
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(src =>
src.Divisions.SelectMany(dv =>
dv.Departments.Where(d => d.Name == departmentName)
)
.Single().Location));
}
}
Is it possible to move the code to select the department to another (private) method within the profile class?
This code will also be needed in the mapping of other members of the DTO.
I tried to move the code to another method, but then the Department's collection of a company is not populated. Probably because EF can't translate the method to SQL. I also tried to rewrite the method to an expression of Func that returns a department, but then I can't use the result directly to access the department properties in the mapping.
public class MyProfile : Profile
{
public MyProfile()
{
string departmentName = null;
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(src =>
SelectDepartment(src, departmentName).Location)
);
}
private Department SelectDepartment(Company company, string departmentName)
{
var department = company.Divisions
.SelectMany(Departments.Where(d => d.Name == departmentName)
.First();
if (department == null)
throw new AutoMapperMappingException($"Department '{departmentName}' not found!");
return department;
}
}
Any suggestions?
It's a half of a solution for you, but I will post it anyway. You can get the mapping done by creating a method that provides an expression. The problem is - how to throw an exception if the fitting department wasn't found, since you cannot throw one in an expression. What I would suggest is to throw the exception after the mapping since you have to query for the data anyway.
public class MyMappingProfile : Profile
{
public MyMappingProfile()
{
string departmentName = "Foo";
CreateMap<Company, MyDto>()
.ForMember(m => m.DepartmentLocation, opt => opt.MapFrom(SelectDepartment(departmentName)));
}
private Expression<Func<Company, string>> SelectDepartment(string departmentName)
=> (company) => company.Divisions
.SelectMany(division => division.Departments)
.Where(department => department.Name == departmentName)
.FirstOrDefault()
.Location;
}
EF Core will generate nice query:
SELECT(
SELECT TOP(1) [d0].[Name]
FROM [Division] AS[d]
INNER JOIN [Department] AS [d0] ON [d].[Id] = [d0].[DivisionId]
WHERE ([c].[Id] = [d].[CompanyId])
AND ([d0].[Name] = #__departmentName_0))
AS [DepartmentLocation]
FROM [Company] AS[c]
Usage:
var result = dbContext
.Set<Company>()
.ProjectTo<MyDto>(mapperConfiguration)
.ToList();
result.ForEach(myDto =>
{
if (myDto.DepartmentLocation == null)
{
throw new AutoMapperMappingException("Department was not found!");
}
});

AutoMapper ProjectTo: Not working with nested objects

I have the following dto:
public class SingleForm
{
// other props left out for brevity
public List<Filter> Filters { get; set; }
}
I then try mapping it with AutoMapper like so:
CreateMap<Form, SingleForm>()
.ForMember(dest => dest.Filters,
opts => opts.MapFrom(src =>
Mapper.Map<List<Filter>>(src.Questions)));
CreateMap<FormQuestion, Filter>()
.ForMember(dest => dest.Header,
opts => opts.MapFrom(src => src.Question.QuestionText));
I then use ProjectTo:
var query = this.context.Forms
.Where(e => e.Id == message.FormId)
.ProjectTo<SingleForm>()
.FirstOrDefault();
However, my filters collection is empty when I execute the query.
When I try to manually map the collection using LINQ, like below, it works correctly, so I'm wondering if I am doing something wrong?
var query = this.context.Forms
.Where(e => e.Id == message.FormId)
.Select(e => new SingleForm
{
Id = e.Id,
Filters = e.Questions.Select(q =>
new Filter {
Header = q.Question.QuestionText
}).ToList()
})
.FirstOrDefault();
In general, I think it is best to avoid calling Mapper.Map() within your profile configuration. With this in mind, I think changing your first mapping to the following may help:
CreateMap<Form, SingleForm>()
.ForMember(dest => dest.Filters,
opts => opts.MapFrom(src => src.Questions));
If the mapping happens out side DbContext then should using includes method to retrieve all relationships items which nit able to lazy load without DbContext.

loading related entities

We are using EF .NET core for our architecture and want to do a basic query. So all we are after is using both LINQ & EF with lazy loading switched off to select the parent, in this case stick item and some of the fields in the child objects. Then return them back into our strongly typed item.
Something like this.
var qry = _context.Set<stock>()
.Include(p => p.stockitem)
.Where(q => q.customer == accountNo)
.Select(r => new stock() {
customer = r.customer,
r.stockitem.select(s => new {id, s.id, s.name }})
.ToList();
So is it possible to do this? basically get hold of say just a couple of columns from our child object. Then have everything returned in the strongly typed object.
First create a model in which the selected data will be stored (This model is just an example):
public class MyNewCustomer
{
public string CustomerAccount { get; set; }
public Dictionary<int, string> Items { get; set; }
}
After that you can create a new object from your select:
var data = _context.Stocks
.Include(p => p.stockitem)
.Where(p => p.customer == accountNo)
.Select(p => new MyNewCustomer
{
CustomerAccount = p.customer,
Items = p.stockitem.ToDictionary(s => s.id, s => s.name)
}).ToList();
PS: I used Dictionary because you didn't provide the actual model. You can choose whatever kind of List<TObject> you want.

c# pass class as a parameter

I want to pass a class as parameter.
So what I want to do is the pass a class, for example: "Customer" to a method.
I want to do this because then I can also pass, for example: "Contract" as class to the same method. This way I don't need to make a method for every class.
Side info: I am using EntityFramework 6, MVC 5, Nest 1.0 and ElasticSearch 1.4
The concept is that we place stuff in ElasticSearch and that we then can do a search.
The search query is:
SearchElasticClient.Search<Customer>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
And for contract:
SearchElasticClient.Search<Contract>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
As you can see, if I want to do a search for every type we have, then I need to copy paste this query like 20 times at least.
I don't like copy pasting because the code is not proper and when I need to change it, it will be a lot of work.
So I want to create a method that takes my class as argument or something like that so that I can make a generic method that reuses this block of code.
So for our example:
My (Enitity Framework) classes:
public class Customer{
public int CustomerID {get;set;}
public String CustomerName {get;set;}
}
public class Contract{
public int ContractID {get;set;}
public String ContractName {get;set;}
}
relation(s) between the classes is for me irrelavent so I left them out.
Then in my HomeController I would like something like
public class HomeController : Controller
{
...
public ActionResult Search(String textToSearch)
{
//So here you see that I want to use the same method for both classes.
Customer customer = Helpers.SearchHelper.Search(textToSearch);
Contract contract = Helpers.SearchHelper.Search(textToSearch);
}
}
Then my SearchHelper would be something like:
public static class SearchHelper
{
public static ElasticClient SearchElasticClient
{
get
{
Uri uri = new Uri("http://localhost:9200");
var setting = new ConnectionSettings(uri, "default_INDEX");
return new ElasticClient(setting);
}
}
public static void SearchTest(String textToSearch, MyClass)
{
var test = SearchElasticClient
.Search<Customer>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
}
As you can see, now I set my class "Customer" fixed in my code.
I want to replace that with a variable or something.
Now what I have tried:
public static void SearchTest<T>(String textToSearch)
{
var test = SearchElasticClient
.Search<T>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
Here I get the compile error: "Cannot convert lambda expression to type 'Nest.ISearchRequest' because it is not a delegate type."
I am not familiar with delegation and how it works and if I can use it, so if delegation is something I need, please provide me enough details.
I also tried:
public static void SearchTest(String textToSearch, Type myClass)
{
var test = SearchElasticClient
.Search<myClass>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXXX"))))));
}
Then it gives me the compile error: "The Type or namespace 'myClass' could not be found." I understand why I get this error, so I know that it will be more something like public static void Search(..){..} but I have no idea how to implement it.
I hope this is a better explanation about my problem.
So it is an implemantion of the "Nest" search and I want to avoid copy pasting the search query.
Thanks in advance
I believe what you want to do is make Search generic
public static classToPass Search<classToPass>()
Then use it like this
Test x = Helper.Search<Test>(); //Test = class as definied above
TestTwo y = Helper.Search<TestTwo>();
Make the Search method generic. A generic argument is, more or less, a parameter that is a type, rather than an object.
public static class Helper
{
public static object Search<T>(T classToPass)
{
SearchElasticClient
.Search<T>(body => body
.AllIndices()
.Size(500)
.Query(query => query
.Bool(#bool => #bool
.Must(must => must
.QueryString(qs => qs
.Query("XXX"))))));
}
}

Creating a custom aggregate function in C# using LINQ

Consider the following class:
public class TaxType
{
public int Id {get;set;}
public decimal TotalTaxCollected {get;set;}
public string DetailXml {get;set;}
}
I got the following LINQ query somewhere in the code:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Aggregate(SumOfInnerElementsOfXml).DetailXml
})
.ToList();
Where SumOfInnerElementsOfXml is declared as follows:
private TaxType SumOfInnerElementsOfXml(TaxType t1, TaxType t2)
{
//(a) Deserialize the t1.DetailXml & t2.DetailXml into 2 different arrays.
//(b) Aggregate both arrays based on their IDs.
//(c) Serialize the result into an xml string.
//(d) Instantiate a new TaxType and set its DetailXml to the generated xml string.
//return (d)
}
The above solution works fine, however I'd like to create my own aggregate function so that I can use it as follows:
GetTaxTypesFromTheDataSource()
.Where(/*blah blah blah*/)
.GroupBy(t => new { t.Id })
.Select(g => new TaxType
{
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.MyCustomAggregateFunction(n => n.DetailXml)
})
.ToList();
How's that possible? I've tried several extension methods but unfortunately none of them worked.
Your initial solution seems pretty fine to me. However, if you don't like it...
To create a predefined function (not lambda), I'd recommend first to get rid of anonymous type in your query - change:
.GroupBy(t => new { t.Id })
to
.GroupBy(t => t.Id)
This is also generally better, as you get rid of one boxing/unboxing per TaxType and transform IGrouping<{anonymous},TaxType> into IGrouping<int,TaxType> (easier to understand the semantics if someone inspects the code later)
Then, you can declare the method as follows:
public static class Extensions {
public static string MyCustomAggregateFunction(this IGrouping<int,TaxType> source, Func<TaxType,string> selector) {
// blah-blah-blah
//return something
}
}
You can later make it generic if the need arises.
I'd also recommend getting rid of the dependency on IGrouping, as you later may need to apply that aggregation elsewhere:
TotalTaxCollected = g.Sum(n => n.TotalTaxCollected),
DetailXml = g.Select(n => n.DetailXml).AggregateTaxXML()
and
public static string AggregateTaxXML(this IEnumerable<string> source) {
// blah-blah-blah
//return something
}
Sources/further reading: MSDN

Categories