Passing table join results to a view? - c#

I have the following ASP.Net MVC controller action which joins 2 tables:-
public ActionResult PersonNotes()
{
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new { note, person })).ToList();
return View(model);
}
And in my view I have the following model declaration:-
#model IEnumerable<Tuple<Models.Note, Models.Person>>
I get the following error:-
System.InvalidOperationException: The model item passed into the dictionary is of type 'System.Collections.Generic.List`1[<>f__AnonymousTypef`2[Models.Note,Models.Person]]', but this dictionary requires a model item of type System.Collections.Generic.IEnumerable`1[System.Tuple`2[Models.Note,Models.Person]]'.
I realise that I can just use a ViewModel and a Select() within my join, but it would far more convenient to just have access to all items without having to create a ViewModel.
What is the correct declaration in my view, or is what I am trying to achieve not possible this way?

You are returning an anonymous object and your view wants a model with a Tuple. The anonymous type is generated by the compiler and is not available at the source code level.
Try changing your statement to create an IEnumerable<Tuple<Models.Note, Models.Person>> with Tuple.Create:
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => Tuple.Create(note, person))).ToList();
See Tuple.Create.
If you are using Linq to Entities or Entity Framework then you will need to either iterate the IQueryable into a Tuple or use a class.
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new { note, person }))
.AsEnumerable()
.Select(x => Tuple.Create(x.note, x.person))
.ToList();
Or
Create a class to hold the Person and Note.
public class PersonNote
{
public Person Person { get; set; }
public Note Note { get; set; }
}
Change the statement to use the new PersonNote.
var model = db.Notes
.Join(db.People, p => p.NotesListId, n => n.NotesListId,
((note, person) => new PersonNote { Note = note, Person = person }))
.ToList();
Change the model.
#model IEnumerable<PersonNote>

Related

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.

"The entity or complex type cannot be constructed in a LINQ to Entities query" in Controler

i have this code in my controller
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new SellingItem
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
and when i try to run it i get
an error
The entity or complex type cannot be constructed in a LINQ to Entities query
now it look to me that my linq query is too complex for entity framework to handle,
so i have 2 workarounds, but i don't like both of them.
1.
load the whole table in memory and make the query like this
2.
use a SQL statement, which will be faster but will not follow the entity framework rules
is there a better way?
-----UPDATE---------------------------------
as it turn out (thanks a lot guys)
i was wrong by stating
now it look to me that my linq query is too complex for entity framework to handle,
and it didn't work because of the fact that i used the same class for the result. i created a new class and now it works amazing!!
You don't need to resort to any of your workarounds to fix that exception per se. The problem is that SellingItem is a class that is part of your Entity Framework model. The reason for this is explained in the comments on this answer.
Either select an anonymous object like so:
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});
Or create an object specifically for the select that you are trying to do:
public class NewClass
{
public ItemClass Item { get;set; }
public ConditionClass Condition { get;set; }
public in ExpInDays { get;set; }
public int Qty { get;set; }
}
Naturally you will need to make sure that the types in this specific class match up to their respective types.
You can then use the new class to do the select:
// Use new class
IQueryable<SellingItem> sellingitems = db.SellingItems
.GroupBy(s => new { s.ItemId ,s.Condition,s.ExpInDays})
.Select(si => new NewClass
{
Item = si.First().Item,
Condition = si.First().Condition,
ExpInDays = si.First().ExpInDays,
Qty = si.Sum(i => i.Qty),
});

Code a SQL projection and mapping at the same time?

This works to carve out a DDL object from an Address object from our database:
public class DDL {
public int? id { get; set; }
public string name { get; set; }
}
List<DDL> mylist = Addresses
.Select( q => new DDL { id = q.id, name = q.name })
.ToList();
However, we'd like to keep our POCO to ViewModel mappings in a single place outside of our MVC controller code. We'd like to do something like this:
List<DDL> mylist = Addresses
.Select( q => new DDL(q)) // <-- constructor maps POCO to VM
.ToList();
But SQL cannot use the constructor function. The object initializer above doesn't use functions to map fields. Of course you could do .AsEnumerable().Select( q => new DDL(q)), but this selects all the fields in SQL (including the data), sends it to C#, then C# carves out the fields we need (terribly inefficient to transfer data we don't need.)
Any suggestions? We happen to be using Entity Framework 6 to pull data.
All you need is to define the expression somewhere and use it. For example, in your ViewModel as a static read-only field.
public class SomeViewModel
{
public static readonly Expression<Func<SomeEntity, SomeViewModel>> Map = (o) => new SomeViewModel
{
id = o.id,
name = o.name
}
public int id { get; set; }
public string name { get; set; }
}
// Somewhere in your controller
var mappedAddresses = Addresses.Select(SomeViewModel.Map);
I personally made myself a little static Mapper that keeps all the maps and use them for me. The maps are declared in a static initializer in all my ViewModels. The result gives me something that feels like AutoMapper, yet doesn't require the lib or the complicated mapping code (but also won't do any magic for you).
I can write something like this:
MyCustomMapper.Map<Entity, ViewModel>(entity);
and it's overloaded to accept IEnumerables, IQueryables and a single ViewModel. I also added overloads with only 1 generic type (the entity) that accept a type parameter. This was a requirement for me.
You can use anonymous types to restrict what to select from the DB and then use those fields to construct your object :
List<DDL> mylist = Addresses
.Select( q => new { id, name })
.AsEnumerable()
.Select(i => new DDL(i.id, i.name) // <- On the Enumerable and not on the Queryable
.ToList();
Are you against using 3rd party libraries? Automapper's QueryableExtensions does exactly what you want.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.ToList();
It even has nice features like being able to filter on the transformed object and that filter being performed server side.
List<DDL> mylist = Addresses
.Project().To<DDL>()
.Where(d => d.name = "Smith") //This gets translated to SQL even though it was performed on DDL.
.ToList();

Properties stuck one level down

I am not having a problem so much with the query as accessing the data or setting it up so I can pass it to the view.
Here's the expression
var distinctReplies = pd.Project.ProjectDoc
.SelectMany(i => i.Comment
.SelectMany(k => k.CommentReply
.Select(u => u.User)
).Distinct()
).Select(g => new {FirstName = g.FirstName, LastName = g.LastName, UserID = g.UserID})
.ToList();
After this expression I want to concat it with another one that is getting values from the same user model, I want to assign distinctReplies to a ViewBag variable and then be able to loop though it and do this
foreach (var user in #ViewBag.distinctReplies) in a razor view.
However, to actually get at the values I have to do distinctReplies.Select(i => i.FirstName). Not sure how to deal with this.
I would suggest to create some ViewModel object for populating the query result. To be able to access the properties inside the #foreach loop.
Model
public class UserVM
{
public int UserID{get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
}
Updated query
var distinctReplies = ....
.Select(g => new UserVM {FirstName = g.FirstName,
LastName = g.LastName,
UserID = g.UserID}).ToList();
Then, in the view, you will need to add cast to IEnumerable<UserVM>.
View
#foreach (var user in (IEnumerable<UserVM>) ViewBag.distinctReplies)
Use this:
#foreach (var reply in (IEnumerable)ViewBag.distinctReplies)
{
}
You cannot enumerate an instance of a dynamic. Cast it to an IEnumerable for the statement to be allowed.
See this question for a more thorough explanation.

Not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression

I have looked at all the examples related to this but have not been able to solve my issue.
I am creating a dropdownlist in asp .net mvc3.
I have a repository which returns:
public IEnumerable<SelectListItem> GetPropertyTypeSelectList()
{
var propertyTypes = from p in db.PropertyType
orderby p.PropertyTypeDescription
select new SelectListItem
{
Text = p.PropertyTypeDescription,
Value = p.PropertyTypeId.ToString()
};
return propertyTypes;
}
My viewmodel looks like this:
public class AddPropertyViewModel
{
public Property Property { get; set; }
public IEnumerable<SelectListItem> PropertyTypes { get; set; }
public IEnumerable<SelectListItem> FurnishedTypes { get; set; }
}
My controller for "create" action for HttpGet looks like this:
public ActionResult AddProperty()
{
AddPropertyViewModel viewModel = new AddPropertyViewModel
{
PropertyTypes = websiterepository.GetPropertyTypeSelectList()
};
return View(viewModel);
}
and the view is like this:
<div class="editor-label">
#Html.LabelFor(model => model.Property.PropertyType)
#Html.DropDownListFor(model => model.Property.PropertyType, Model.PropertyTypes)
</div>
I am getting the error above. From what I have read it looks like ToString() is causing the problem. But I am not sure how to correct it.
Thanks.
LINQ to SQL doesn't know how to translate the .ToString() call to a SQL expression.
So replace:
var propertyTypes =
from p in db.PropertyType
orderby p.PropertyTypeDescription
select new SelectListItem
{
Text = p.PropertyTypeDescription,
Value = p.PropertyTypeId.ToString()
};
with:
var propertyTypes = db
.PropertyType
.OrderBy(x => x.PropertyTypeDescription)
.ToList()
.Select(x => new SelectListItem
{
Text = p.PropertyTypeDescription,
Value = p.PropertyTypeId.ToString()
});
Notice the .ToList() call to eagerly execute the SQL query after building the expression up until the OrderBy clause and then do the .ToString() on the client (LINQ to Objects instead of LINQ to Entities) where the .ToString() expression is perfectly supported.
So here basically we are constructing a SQL query up until the OrderBy clause (including) and then will call .ToList to eagerly execute this query and fetch the resultset on the client. Then we continue chaining with a .Select statement. But we are no longer doing any LINQ to Entities or SQL stuff. We are now doing LINQ to Objects because all the resultset is now in-memory. And doing .ToString in LINQ to Objects doesn't pose any challenge.
Another possibility is to use the SqlFunctions.StringConvert built-in function that knows how to translate it to SQL. This way you are keeping it lazy:
var propertyTypes =
from p in db.PropertyType
orderby p.PropertyTypeDescription
select new SelectListItem
{
Text = p.PropertyTypeDescription,
Value = SqlFunctions.StringConvert((double)p.PropertyTypeId)
};

Categories