I recently started to use LINQ to SQL and i have a minor complex query i need help with.
I've got a table in my database called MovieComment, with the following columns:
CommentID
UserID
MovieID
Comment
Timestamp
So, what i wanna do is to group the comments on MovieID and save them into my object called Movie, where the MovieID is being saved in the MovieID post, and the Linq object is saved inside the ObservableCollection inside the Movie object.
public class Movie
{
#region Member Variables
public int MovieID { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Uri Poster { get; set; }
public double Rating { get; set; }
public DateTime Timestamp { get; set; }
public ObservableCollection<MovieComment> Comments { get; set; } // Linq object: MovieComment
#endregion // Member Variables
}
I've come up with the following linq query where i get the MovieID, but i dont really know how i should proceed to get a hold of all the other data
public ObservableCollection<Movie> LoadMovieID(int _userID, int _limit)
{
ObservableCollection<Movie> movies = new ObservableCollection<Movie>();
var query = (from mc in db.MovieComment
where mc.UserID == _userID
orderby mc.Timestamp descending
group mc by mc.MovieID into movie
select new
{
MovieID = movie.Key,
}).Take(_limit);
foreach (var row in query)
{
Movie movie = new Movie();
movie.MovieID = row.MovieID;
// I want to get the following:
// movie.MovieComment = MovieComment-objects with the MovieID == row.MovieID
movies.Add(movie);
}
return movies;
}
Is this even possible in a single query? Thankful for all the help i can get
Well, you could try this:
var query = (from mc in db.MovieComment
where mc.UserID == _userID
orderby mc.Timestamp descending
group mc by mc.MovieID).Take(_limit);
That will give you a IGrouping<MovieComment, string> (or whatever your types are) which should let you get at all the comments without any extra work. It's certainly okay in terms of LINQ itself, but whether it will do what you want within LINQ to SQL, I'm not sure.
Related
While using LinQ, retrieving data from foreign key table data are available. But when I try to 'Add' into my ViewModel this warnings shows. Warningsare difference from each other. such as,
Cannot implicitly convert type 'System.Collection.Generic.IEnumerable<string>' to 'string'
and
Cannot implicitly convert type 'System.Collection.Generic.IEnumerable<System.Collection.Generic.IEnumerable.IEnumerable<string>' to 'string'.
I tried casting to ToString() but it was worthless, no errors shows but data was replaced with system messages. I also tried to LinQ Join in students but I was unable to show Skills Comma Separated that way.
Here is my code:
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
coutryName=sg.Select(c=>c.Country.CountryName),
cityName=sg.Select(ct=>ct.Country.Cities.Select(x=>x.CityName)),
skillName=sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName)),
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName)),
dob=sg.Select(d=>d.DateOfBirth)
}).ToList();
List<StudentListVM> studentLists=new List<StudentListVM>();
foreach (var item in students)
{
studentLists.Add(new StudentListVM
{
studentName = item.studentName,
country = item.coutryName, //warnings here
city = item.cityName, //warnings here
skills = string.Join(",", item.skillName),
resume = item.resumeName, //warnings here
dateOfBirth = item.dob //warnings here
});
}
return View(studentLists);
}
```
StudentListVM class
public class StudentListVM
{
public string studentName { get; set; }
public string country { get; set; }
public string city { get; set; }
public string skills { get; set; }
public string resume { get; set; }
public DateTime dateOfBirth { get; set; }
}
```
I tried this before
var students = (from stud in db.Students
join con in db.Countries on stud.CountryId equals con.CountryId
join ct in db.Cities on stud.CityId equals ct.CityId
join rsm in db.Resumes on stud.ResumeID equals rsm.ResumeId
join stsk in db.StudentSkills on stud.StudentId equals stsk.StudentId
//group stud by stud.StudentId into sg
select new StudentListVM()
{
studentName = stud.StudentName,
countries = con.CountryName,
cities = ct.CityName,
skills=stsk.Skill.SkillName,
resumes = rsm.ResumeName,
dateOfBirth = stud.DateOfBirth,
}).ToList();
```
StudentSkill class:
public partial class StudentSkill
{
public int StudentSkillsId { get; set; }
public int StudentId { get; set; }
public int SkillId { get; set; }
public virtual Student Student { get; set; }
public virtual Skill Skill { get; set; }
}
```
This returns All fine except The Skills in a comma separated list. All I need to show my multiple skills that are checked multiply and added to the database in a separated table name StudentSkills. Is there any good solution to do it?
You are trying to assign a group of strings, specifically the various IEnumerable<string> collections, into a single string due to your Select() calls.
For example, this line is clearly selecting more than one resume name.
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName))
If you don't care and expect them to be all the same value you could just grab the first one:
resume = item.resumeName.FirstOrDefault()
Or flatten the collection some other way.
That said, there's something off with the design when you grab a collection and try to assign it to a single item.
Try to change this line:
skills = string.Join(",", item.skillName),
With this:
skills = string.Join(",", item.skillName.ToArray()),
The problem you have is not in your code. The problem is how you think you are solving it. Think it right, the solution will be right. #Zer0 already mentioned that but you probably need more explanation. I will try to explain with some assumption that you might be wanting to do --
1: If Student can have multiple country, city, skill and resumes, then the StudentVM class you have is most certainly wrong. By definition it only supports one city, country, skill, etc. Modify it to support multiples -
public class StudentListVM
{
public string studentName { get; set; }
public List<string> countries { get; set; }
public List<string> cities { get; set; }
public string skills { get; set; }
public List<string> resume { get; set; }
//does not make sense to have a list, a person has only one DOB
public DateTime dateOfBirth { get; set; }
}
then the code you have will work -
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
coutryName=sg.Select(c=>c.Country.CountryName),
cityName=sg.Select(ct=>ct.Country.Cities.Select(x=>x.CityName)),
skillName=sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName)),
resumeName=sg.Select(r=>r.Resumes.Select(m=>m.ResumeName)),
dob=sg.Select(d=>d.DateOfBirth).FirstOrDefault()
}).ToList();
List<StudentListVM> studentLists=new List<StudentListVM>();
foreach (var item in students)
{
studentLists.Add(new StudentListVM
{
studentName = item.studentName,
countries = item.coutryName.ToList(), //should work, as these are lists
cities = item.cityName.ToList(), //should work, as these are lists
skills = string.Join(",", item.skillName),
resume = item.resumeName.ToList(), //should work, as these are lists
dateOfBirth = item.dob //does not make sense to have a list, a person has only one DOB
});
}
return View(studentLists);
}
2: Once the class is okay, you could shorten the code. You don't need a second block to create a typed list, you can do it directly -
public ActionResult GetStudentsInfo ()
{
var students = (from stud in db.Students
group stud by stud.StudentId into sg
select new StudentListVM
{
studentName=sg.Select(s=>s.StudentName).FirstOrDefault(),
countries=sg.Select(c=>c.Country.CountryName).ToList(),
cities=sg.SelectMany(ct=>ct.Country.Cities.Select(x=>x.CityName)).ToList(),
skills=string.Join(",", sg.Select(sk=>sk.StudentSkills.Select(s=>s.Skill.SkillName))),
resume=sg.SelectMany(r=>r.Resumes.Select(m=>m.ResumeName)).ToList(),
//does not make sense to have a list, a person has only one DOB
dob=sg.Select(d=>d.DateOfBirth).FirstOrDefault()
}).ToList();
return View(students);
}
3: If the above does not make sense, then the idea is not right. Think of what you are trying to achieve and update the question. May be then people will be able to help.
You have said -
Actually I can do them separately in different ways but I can't do it in a single code
So what are those ways? Mentioning them will probably give an idea of what you are trying to achieve. The details on the question is not enough to give you solution. It does not say what you are trying to do.
I have two tables LookUpCodes and LookUpValues they are defined as below:
public partial class LookUpCodes
{
public int Id { get; set; }
public int? CodeId { get; set; }
public string Code { get; set; }
public string Description { get; set; }
}
public partial class LookUpValues
{
public int Id { get; set; }
public int CodeId { get; set; }
public string CodeValue { get; set; }
public bool? IsActive { get; set; }
}
Each LookUpCode can have multiple Values associated with it. I want to pass in a code and get associated list of values back.
This is probably a common question as I have seen this everywhere, I am not looking for an answer per se, if someone can just explain how to build the proper query I would be obliged.
Here is what I have done so far:
public IEnumerable<LookUpValues> GetValuesByCode(string cd)
{
var query = from code in _context.LookUpCodes
join values in _context.LookUpValues on code.CodeId equals values.CodeId
where code.Code == cd
select new { LookUpValues = values };
return (IEnumerable<LookUpValues>) query.ToList();
}
You are very close to that you are looking for:
public IEnumerable<LookUpValues> GetValuesByCode(string cd)
{
var query = from code in _context.LookUpCodes
join values in _context.LookUpValues
on code.CodeId equals values.CodeId
where code.Code == cd
select values;
return query;
}
Since you have written the join, I assume that you have understood how it works. However let's revisit it:
from a in listA
join b in listB
on a.commonId equals b.commonId
In the above snippet we join the contents of listA with the contents of listB and we base their join on a commonId property existing in items of both lists. Apparently the pair of a and b that fulfill the join criterion it would form one of the possible many results.
Then the where clause is applied on the results of the join. The joined items that pass thewherefilter is the new result. Even at this point the results is still pairs ofaandb`.
Last you project, using the select keyword each pair of the results to a new object. In your case, for each pair of code and values that passed also the where filter, you return only the values.
I really love Dapper's simplicity and possibilities. I would like to use Dapper to solve common challenges I face on a day-to-day basis. These are described below.
Here is my simple model.
public class OrderItem {
public long Id { get; set; }
public Item Item { get; set; }
public Vendor Vendor { get; set; }
public Money PurchasePrice { get; set; }
public Money SellingPrice { get; set; }
}
public class Item
{
public long Id { get; set; }
public string Title { get; set; }
public Category Category { get; set; }
}
public class Category
{
public long Id { get; set; }
public string Title { get; set; }
public long? CategoryId { get; set; }
}
public class Vendor
{
public long Id { get; set; }
public string Title { get; set; }
public Money Balance { get; set; }
public string SyncValue { get; set; }
}
public struct Money
{
public string Currency { get; set; }
public double Amount { get; set; }
}
Two challenges have been stumping me.
Question 1:
Should I always create a DTO with mapping logic between DTO-Entity in cases when I have a single property difference or simple enum/struct mapping?
For example: There is my Vendor entity, that has Balance property as a struct (otherwise it could be Enum). I haven't found anything better than that solution:
public async Task<Vendor> Load(long id) {
const string query = #"
select * from [dbo].[Vendor] where [Id] = #id
";
var row = (await this._db.QueryAsync<LoadVendorRow>(query, new {id})).FirstOrDefault();
if (row == null) {
return null;
}
return row.Map();
}
In this method I have 2 overhead code:
1. I have to create LoadVendorRow as DTO object;
2. I have to write my own mapping between LoadVendorRow and Vendor:
public static class VendorMapper {
public static Vendor Map(this LoadVendorRow row) {
return new Vendor {
Id = row.Id,
Title = row.Title,
Balance = new Money() {Amount = row.Balance, Currency = "RUR"},
SyncValue = row.SyncValue
};
}
}
Perhaps you might suggest that I have to store amount & currency together and retrieve it like _db.QueryAsync<Vendor, Money, Vendor>(...)- Perhaps, you are right. In that case, what should I do if I need to store/retrive Enum (OrderStatus property)?
var order = new Order
{
Id = row.Id,
ExternalOrderId = row.ExternalOrderId,
CustomerFullName = row.CustomerFullName,
CustomerAddress = row.CustomerAddress,
CustomerPhone = row.CustomerPhone,
Note = row.Note,
CreatedAtUtc = row.CreatedAtUtc,
DeliveryPrice = row.DeliveryPrice.ToMoney(),
OrderStatus = EnumExtensions.ParseEnum<OrderStatus>(row.OrderStatus)
};
Could I make this work without my own implementations and save time?
Question 2:
What should I do if I'd like to restore data to entities which are slightly more complex than simple single level DTO? OrderItem is beautiful example. This is the technique I am using to retrieve it right now:
public async Task<IList<OrderItem>> Load(long orderId) {
const string query = #"
select [oi].*,
[i].*,
[v].*,
[c].*
from [dbo].[OrderItem] [oi]
join [dbo].[Item] [i]
on [oi].[ItemId] = [i].[Id]
join [dbo].[Category] [c]
on [i].[CategoryId] = [c].[Id]
join [dbo].[Vendor] [v]
on [oi].[VendorId] = [v].[Id]
where [oi].[OrderId] = #orderId
";
var rows = (await this._db.QueryAsync<LoadOrderItemRow, LoadItemRow, LoadVendorRow, LoadCategoryRow, OrderItem>(query, this.Map, new { orderId }));
return rows.ToList();
}
As you can see, my question 1 problem forces me write custom mappers and DTO for every entity in the hierarchy. That's my mapper:
private OrderItem Map(LoadOrderItemRow row, LoadItemRow item, LoadVendorRow vendor, LoadCategoryRow category) {
return new OrderItem {
Id = row.Id,
Item = item.Map(category),
Vendor = vendor.Map(),
PurchasePrice = row.PurchasePrice.ToMoney(),
SellingPrice = row.SellingPrice.ToMoney()
};
}
There are lots of mappers that I'd like to eliminate to prevent unnecessary work.
Is there a clean way to retrive & map Order
entity with relative properties like Vendor, Item, Category etc)
You are not showing your Order entity but I'll take your OrderItem as an example and show you that you don't need a mapping tool for the specific problem (as quoted). You can retrieve the OrderItems along with the Item and Vendor info of each by doing the following:
var sql = #"
select oi.*, i.*, v.*
from OrderItem
inner join Item i on i.Id = oi.ItemId
left join Vendor v on v.Id = oi.VendorId
left join Category c on c.Id = i.CategoryId";
var items = connection.Query<OrderItem, Item, Vendor, Category, OrderItem>(sql,
(oi,i,v,c)=>
{
oi.Item=i;oi.Item.Category=c;oi.Vendor=v;
oi.Vendor.Balance = new Money { Amount = v.Amount, Currency = v.Currency};
return oi;
});
NOTE: The use of left join and adjust it accordingly based on your table structure.
I'm not sure I understand your question a 100%. And the fact that no one has attempted to answer it yet, leads me to believe that I'm not alone when I say it might be a little confusing.
You mention that you love Dapper's functionality, but I don't see you using it in your examples. Is it that you want to develop an alternative to Dapper? Or that you don't know how to use Dapper in your code?
In any case, here's a link to Dapper's code base for your review:
https://github.com/StackExchange/dapper-dot-net
Hoping that you'd be able to clarify your questions, I'm looking forward to your reply.
I have the following tables:
I'm using Entity Framework Database First, therefore the following entity class is generated:
public partial class Sal1 {
public string SaleID { get; set; }
public string ItemID { get; set; }
public int Quantity { get; set; }
public decimal Total { get; set; }
public virtual Item Item { get; set; }
public virtual Sale Sale { get; set; }
}
Then put the Sal1 rows into a datagrid like this:
private List<Sal1> saleItems = new List<Sal1>();
...
var query = from sa in db.Sal1
where sa.SaleID.Equals(tempSale)
select sa;
foreach(Sal1 si in query) {
saleItems.Add(si);
}
...
dgDetails.ItemsSource = saleItems;
But it turns out like this:
My question is, how should I tweak the query above so that I get the equivalent of the following SQL:
select T0.SaleID, T0.ItemID, T1.Name, T0.Quantity, T0.Total
from Sal1 T0 inner join Item T1 on T0.ItemID = T1.ItemID;
Thanks in advance.
EDIT: I seem to have found a solution, but I had to do this:
var query = from sa in db.Sal1
where sa.SaleID.Equals(tempSale)
select new { sa.SaleID, sa.ItemID, sa.Item.Name,
sa.Item.Manufacturer, sa.Quantity, sa.Total };
And I had to change the type of saleItems to object.
private List<object> saleItems = new List<object>();
Is this the best way to do it?
Just like SQL, LINQ also supports JOINs. You can read more about their syntax here. You should change your query accordingly to get your results. Instead of spoonfeeding the exact answer, I'm guiding you to a more detailed explanation, as it contains valuable information that will help you in the future too.
I'm creating a product listing for an online store. It's pretty standard stuff, a page of product thumbnails with brief details, price and a link through to full details.
I'm using a repository pattern, so I have a central data repository which gives me back tables from a SQL server. I've cut a lot of the code out for the sake of brevity, but just so you get the idea:
public class SqlProductsRepository : IProductsRepository
{
private Table<Product> productsTable;
public SqlProductsRepository(string connectionString)
{
var context = new DataContext(connectionString);
productsTable = context.GetTable<Product>();
// More tables set up here
}
public IQueryable<Product> Products
{
get { return productsTable; }
}
// More properties here
}
I have the following objects mapped to tables:
[Table(Name = "Products")]
public class Product
{
[Column(IsPrimaryKey = true)]
public string ProductCode { get; set; }
[Column]
public string Name { get; set; }
[Column]
public decimal Price { get; set; }
public List<ShopImage> Images = new List<ShopImage>();
}
[Table(Name = "Images_Products")]
public class Image_Product
{
[Column]
public int ImageID { get; set; }
[Column]
public string ProductCode { get; set; }
[Column]
public int DisplayOrder { get; set; }
}
[Table(Name = "Images")]
public class Image
{
[Column(Name = "ImageID")]
public int ImageID { get; set; }
[Column]
public bool Caption { get; set; }
}
If I perform the following query:
// 'db' is the repository (member variable of the controller class)
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select p;
I get a nice IQueryable full of Product objects. However, what I want to do is populate each object's Images list property with a single Image object, with its ID set from the joined Image_Product table.
So I end up with a list of Products, each with one Image in its Images property, which has the ID of the image for that product in the database where DisplayOrder is 0.
I tried this projection, which I thought made sense:
IQueryable<Product> products = from p in db.Products
join ip in db.Image_Product on p.ProductCode equals ip.ProductCode
where ip.DisplayOrder == 0
select new Product {
ProductCode = p.ProductCode,
Price = p.Price,
Images = new List<Image> {
new Image { ImageID = ip.ImageID }
}
};
Which compiles, but throws a runtime error: Explicit construction of entity type 'xxx.Product' in query is not allowed.
Yet elsewhere in the project I do this:
var pages = from i in db.TemplatePageNavigationItems
orderby i.DisplayOrder
select new NavigationItem {
ID = i.PageID,
ParentID = i.ParentID,
Text = i.Name,
Title = i.Name,
Url = (i.Folder == null) ? "" : i.Folder
};
And get no complaints! I assume it's something to do with the first query returning an IQueryable<Product> but I'm not sure why.
Two questions really: why is this not allowed in the first situation, and what should I be doing in order to get my desired result?
As the error says, you can't construct explicit entity types (Product is just that) in your query which should return IQueryable<Product>. Your pages query will return IEnumerable<NavigationItem> and NavigationItem does not seem to be an entity type defined in the database.
You could try returning IEnumerable<Product> in your first query or define a separate type and return IEnumerable of that instead, if you need to project explicit, custom tailored instances of an object.