Enity Framework query result shaping - asp.net-mvc

Based off the MVC Music Store sample, I'm trying to pre-fetch the albums along with the genre but only read specific data from the album and not the complete entity.
Here is the EF query:
public ActionResult Browse(string genre)
{
// Retrieve Genre and its Associated Albums from database
var genreModel = storeDB.Genres.Include("Albums").Single(g => g.Name == genre);
return View(genreModel);
}
Say I have a View Model with:
public class AlbumViewModel
{
public string Title { get; set; }
public decimal Price { get; set; }
}
How would I alter the EF LINQ query to get both the genre info AND include the albums but only select the data listed in the View Model, not the enitire entity?
To do the View Model part, I would have done something like (VB):
From a In storeDB.Albums
Where a.Genre = genre
Select New AlbumViewModel With {
.Title = a.Title,
.Price = a.Price,
})

This should be it:
(from g in storeDB.Genres
where g.Name == genre
select new {
Genre = g,
Albums = g.Albums.Select(a => new { Title = a.Title, Price = a.Price })
})

Related

Asp.net mvc call entity db data using Id for that particular row

Hi and thank you for taking your time to read. I am having trouble calling from a db using entity framework for a particular row. Here is my code for controller.
public ActionResult MyAccount(CurrentAccount ca, SaverAccount sa, int id)
{
var model = db.CurrentAccounts.FirstOrDefault(_ => _.Id == id);
Session["Id"] = ca.Id;
Session["CurrentAccountNumber"] = ca.CurrentAccountNumber;
Session["CurrentBalance"] = ca.CurrentBalance;
Session["SaverAccountNumber"] = sa.SaverAccountNumber;
Session["CurrentBalance"] = sa.SaverAccountNumber;
return View(model);
}
My model is a edmx entity file and i can seem to retrieve some data to my locals but only from 1 table and i need data to be from multiple tables selecting a full row of data for a paricular Id then having this information visable on the same view. There is also a relation between id on both tables. Thanks :)
Here you have called wrong object because you are fetching data in model variable but calling from ca. please use as following
public ActionResult MyAccount(CurrentAccount ca, SaverAccount sa, int id)
{
var model = db.CurrentAccounts.FirstOrDefault(_ => _.Id == id);
Session["Id"] = model.Id;
Session["CurrentAccountNumber"] = model.CurrentAccountNumber;
Session["CurrentBalance"] = model.CurrentBalance;
Session["SaverAccountNumber"] = sa.SaverAccountNumber;
Session["CurrentBalance"] = sa.SaverAccountNumber;
return View(model);
}
You need to execute join query to get data from two models like following exmaple
Create a common class like follwing
public class datafrombothclass
{
public int Id { get; set; }
public String saveaccount_name { get; set; }
public String currrentaccount_name { get; set; }
}
Now use join query in entity framework to get data from both model in you case from CurrentAccount and SaverAccount.
See the bellow code example:
var frombothclass=(from a in Model.saveaccount join s in Model.currentaccountaccount
where a.Id=id
select new datafrombothclass{
Id=a.Id,
saveaccount_name=s.name,
currrentaccount_name=a.name
});
return View(frombothclass);
Hope you will get the solution.

EF Find entities not in list

I have this entities:
Product
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
}
PriceList
public class PriceList
{
public int Id { get; set; }
public string Name { get;set; }
}
PriceListProduct
public class PriceListProduct
{
public int Id { get; set; }
public int PriceListId { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
Question is, how can I get the products not in Price List using LINQ?
My first thought was the use of Contains, but the list of products could be greater than 100000, if Contains is translate as a query like WHERE NOT IN clause, SQL has an approximate limit of 2000 parameters, so besides performance, I think this is not the best approach.
Is there another way? Should I use raw queries?
Update # 1
I'm trying to understand GroupJoin following #Indregaard answer. So far I have this.
var productsWithNoPrice = db.Product()
.GroupJoin(db.PriceListProduct().Where(plp => plp.PriceListId == 2)
.Select(plp => plp.Product),
p => p.Id,
plp => plp.Id,
(p, product) => new { p.Id, Product = product })
.Where(p => !p.Product.Any())
.Select(p => p.Product);
With the filter
.Where(plp => plp.PriceListId == 2)
I'm filtering products from Price List with Id 2. I think this is close but the query generated by SQL returns a number of rows that corresponding to the number of products that not exists in the Price List but every single column is null.
Basically what I need is a query like this
select * from Product p
left join PriceListProduct plp on plp.ProductId = p.Id and plp.PriceListId = 2
where plp.Id is null
So you are looking for Antijoin.
Manual approach could be like this:
var query =
from p in db.Products
join plp in db.PriceListProducts
on p.Id equals plp.ProductId into priceLists
where !priceLists.Any()
select p;
Another way:
var query = db.Products
.Where(p => !db.PriceListProducts.Any(plp => p.Id == plp.ProductId));
But the best way is to create all navigation properties in the model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<PriceListProduct> PriceLists { get; set; }
}
and let EF create queries for you
var query = db.Products.Where(p => !p.PriceLists.Any());
Have you tried Join/GroupJoin?
I haven't tried against a database to see if the generated sql is valid/working but against regular objects something like this will work.
var productsWithNoPrices = products.GroupJoin(productPriceList,
product => product.Id,
productprice => productprice.ProductId,
(product, productPrice) => new { Product = product, Prices = productPrice})
.Where(c=>!c.Prices.Any()).Select(c=>c.Product);
Edit: Based on your updated question I think you want something like this:
var productsWithNoPrices = db.Products.GroupJoin(db.PriceListProducts.Where(c => c.PriceListId == 2),
product => product.Id,
productprice => productprice.ProductId,
(product, productPrice) => new { Product = product, Prices = productPrice }).Where(c=>!c.Prices.Any()).Select(c=>c.Product);
GroupJoin will take whats in your left table (db.Products), join with whats in your right table (db.PriceListProducts.xxxxx) (parameter 1): each product from the left table will get a list of matches from right, combined on product ids (param 2 and 3), output to a anonymous type (param 4). Filter all this on where there is no productprice and select the products. This results in the following SQL, which seems to give the desired result?
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Name] AS [Name]
FROM [dbo].[Products] AS [Extent1]
WHERE NOT EXISTS (SELECT
1 AS [C1]
FROM [dbo].[PriceListProducts] AS [Extent2]
WHERE (2 = [Extent2].[PriceListId]) AND ([Extent1].[Id] = [Extent2].[ProductId])
)

How do I loop through an array and add each value to my model variable? MVC Razor

I am trying to make a variable in my model that retrieves all available skills to later display them in a DDL. I have one variable that contains all of the skills and one variable that displays all of the skills an employee has already rated. I want to check all of the skills against the rated skills so that only the unrated skills are added to the new variable in the model. The only way I could think of to do this is to use a foreach loop to check for the values in the AllSkills variable.
model:
public IEnumerable<long> AvailableSkills { get; set; }
public IEnumerable<long> AllSkills { get; set; }
public IEnumerable <long> EmployeeRatings { get; set; }
public IEnumerable<Rating> Ratings { get; set; }
public IEnumerable<Skill> Skills { get; set; }
public long SkillId { get; set; }
Controller:
model.AllSkills = db.Skills.OrderBy(s => s.SkillName).Select(s => s.SkillId);
model.EmployeeRatings = db.Ratings.Where(r => r.EmployeeId == User.Identity.Name.Remove(0, 8)).OrderBy(s => s.SkillId).Distinct();
foreach (var skill in model.EmployeeRatings)
{
model.AvailableSkills = model.AllSkills.Where(s => s != skill);
}
I want to do something like the following code in the foreach loop:
model.AvailableSkills += model.AllSkills.Where(s=> s != skill); but this is not allowed.
The problem is that this for each loop above is assigning the model.AvailableSkills to the every skill but the last one that is in the foreach loop (as it should). How do I make it so that every one of the duplicate skills are excluded from model.AvailableSkills?
Using LINQ is IMO more readable if you use to query syntax opposed to the method chaining syntax.
model.AllSkills =
from skill in db.Skills
orderby skill.SkillName
select skill.SkillId;
model.EmployeeRatings =
from rating in db.Ratings
let employeeId = User.Identity.Name.Remove(0, 8)
where rating.EmployeeId == employeeId
orderby rating.SkillId
select rating.SkillId
You can use the Except() extension method to remove items from the collection.
// exclude the 'owned' skils
model.AvailableSkills = model.AllSkills.Except(model.EmployeeRatings);
And you probably want to distinct the results:
model.AvailableSkills = model.AvailableSkills.Distinct();
Last but not least:
Because you select the SkillId I'm unsure why you would order the results. This does not make sense especially because you order by different properties in both lists. Furtermore you probably want to select more details to display to the user, but to know this we need more details on your model.
Assuming EmployeeRatings contains rated skills, and you want AvailableSkills to have only skills not in EmployeeRatings but are in AllSkills, I think this is what you like to do:
model.AvailableSkills = model.AllSkills
.Where(s => !model.EmployeeRatings.Contains(s));
Consider two classes like this:
public class Skill
{
public int Id { get; set; }
}
public class Employee
{
public int[] RatedSkills { get; set; }
}
var skills = new List<Skill>
{
new Skill{ Id = 1}, new Skill{Id = 2}, new Skill{ Id = 3}, new Skill { Id = 4}
};
var emp = new Employee
{
RatedSkills = new int[] { 1,2 }
};
var availableSkills = skills.Select(s => s.Id).Except(emp.RatedSkills);
Console.Read();
The rating has an Id property an an employee has an int[] to hold his/her selected ratings. From there it's easy to filter
Following your current approach, you can use AddRange with a list. Something like:
List<long> availableSkills = new List<long>();
foreach (var skill in model.EmployeeRatings)
{
availableSkills.AddRange(model.AllSkills.Where(s => s != skill));
}
model.AvailableSkills = availableSkills;
Or you can achieve this with a more compact approach, and I believe Except removes dupes:
model.AvailableSkills = model.AllSkills.Except(model.EmployeeRatings);

Adding nested entities to a database

I'm using MVC 4 and Entity Framework 5 Code First.
I have the following entities:
public class File
{
public string Album { get; set; }
public string Genre { get; set; }
}
public class Genre
{
public string GenreId { get; set; }
public virtual List<Album> Albums { get; set; }
}
public class Album
{
public string AlbumId { get; set; }
}
I try to add them to a database:
private readonly List<File> _files = new List<File>();
var genres = _files
.Select(x => new Genre { GenreId = x.Genre })
.GroupBy(x => x.GenreId)
.Select(x => x.First())
.ToList();
foreach (var genre in genres)
{
var genreId = genre.GenreId;
var albums = _files
.Where(x => x.Genre == genreId)
.Select(x => new Album { AlbumId = x.Album })
.GroupBy(x => x.AlbumId)
.Select(x => x.First())
.ToList();
genre.Albums = albums;
_unitOfWork.GenreRepository.Insert(genre);
}
And I get the following error:
Violation of PRIMARY KEY constraint 'PK_dbo.Albums'. Cannot insert duplicate key in object 'dbo.Albums'. The duplicate key value is (Ooz).
The statement has been terminated.
The data I try to add looks like the following:
--Tribal
----Ooz
--Goa Trance
----Ooz
----Transient Dawn
--Progressive Trance
----Ooz
----Music Prostitute: The Remixes
--Psy-Trance
----Music Prostitute: The Remixes
--Downtempo
----Transient Dawn
--Ambient
----Transient Dawn
I see that I'm trying to add each album many times (because each album has many genres) but I don't know how to solve that issue.
Thanks for help!
When creating the list of albums, try to get an album from the database before creating it.
Try this:
var albums = _files
.Where(x => x.Genre == genreId)
.Select(x => _yourDBContext.Albums.Where(album=>album.AlbumId == x.Album).FirstOrDefault() ??
new Album { AlbumId = x.Album })
.GroupBy(x => x.AlbumId)
.Select(x => x.First())
.ToList();
The ?? is the null-coalescing operator, it chooses the second value if the first is null. I'm not sure, but hope this will help.
Instead of parsing into List<File> just insert into database at that step. Of course, you'll need to look to see if the genre exists, and if it does then insert files under that genre, if not then insert a new genre with the files already in the List<Album> Albums
Since you can do all of this in one context, entity will be very efficient for you. What I mean is that entity will track whether things are in the database or not and you can just build the tree of genres and albums and call one save changes.

Retrieving data from a one-to-many relationship

I'm trying to retrieve data from a one-to-many relationship, in an ASP.NET MVC application.
Let's say I have two tables Posts and Category with the following attributes:
posts
-id
-title
-category_id
category
-id
-name
-author
I want to grab posts that belong to a category and grab the values of .name and .author from the category.
This is what I have in the model:
public IQueryable<Post> GetPosts()
{
return from post in db.Posts
join categories in FindCategories()
on categories.id equals post.category_id
select new
{
title = post.title,
name = categories.name,
author = categories.author
};
}
And in the controller:
public ActionResult Index()
{
var allposts = postRepository.GetPosts().ToList();
return View(allposts);
}
The model complaints that it can't convert and IQueryable of Anonymous type into an IQueryable < Post >. I suspect it's because I'm doing Select new { }, but I'm not sure.
If I do IQueryable only then I don't have the ToList() method available in the view.
So, I'm lost. Can you help?
(an by the way, I'm a complete newbie at this asp.net mvc thing.)
I think the problem is that you're trying to use a var outside of the scope of a single method. In your projection stage try newing-up a Post object rather than an anonymous type. e.g.
public IQueryable<Post> GetPosts()
{
return from post in db.Posts
join categories in FindCategories()
on categories.id equals post.category_id
select new Post()
{
title = post.title,
name = categories.name,
author = categories.author
};
}
The question has been answered by Lazarus in the comments. Your method declaration states a return type of IQueryable of Post types
public IQueryable<Post> GetPosts() { //...
But your linq projects to an anonymous type and returns an IQueryable of anonymous types
select new { //anonymous type
title = post.title,
name = categories.name,
author = categories.author
}; // = IQueryable<SomeCompilerGeneratedTypeNameHere>
You need to define a new type
public class PostDisplay {
public string Title { get; set; }
public string Name { get; set; }
public string Author { get; set; }
}
then in your code return an IQueryable of that type
public IQueryable<PostDisplay> GetPosts() {
return from post in db.Posts
join categories in FindCategories()
on categories.id equals post.category_id
select new PostDisplay {
Title = post.title,
Name = categories.name,
Author = categories.author
};
}

Resources