linq to sql and retrieving only specific rows from joined tables - asp.net

I have two tables 1) Product 2) Categories
Product table has a field called "CategoryID" and the Category table has a field called "CategoryName", ID, DateCreated, and a few others.
I want to use LINQ to SQL to query all the Product rows as well as JUST the Category.CategoryName. This query will be used in a datasource and be bound to a ListView.
I know I can use the LoadWith option with the DataContext and a join. But the problem is that I end up with all the rows from Product and Category. What I really want is all rows from Product and just the Category.CategoryName
The perfect solution would be if the LoadWith option would let you specify the fields to return but as answered here, that's not possible.
I did find one way to do it, create a custom class which has the same exact fields that I want returned. So for example
public class ProductInfo
{
public string ProdName { get; set; }
public int ProdID { get; set; }
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
but the problem is that the select statement ends up really long. for example...
var products = from p in db.Products
join c in db.Categories on p.CategoryID = c.ID
select new ProductInfo { ProdName = p.ProdName, ProdID = p.ID,
CategoryID = p.CategoryID, CategoryName = c.CategoryName }
this becomes really error prone.
So my question - is there a better way to accomplish this?
Edit/Clarification:
i should have mentioned that the REASON i need the intermediary class is because i need to be able to return the result set as a specific type, in this case ProductInfo

I think you're pretty much on the right track, but I wouldn't really worry about the intermediery class:
var products = from p in db.Products
join c in db.Categories on p.CategoryID = c.ID
select new { p.ProdName, ProdId = p.ID, p.CategoryID,
CategoryName = c.CategoryName }
You can then wire this up in the Selecting event of the LinqDataSource. Scott Guthrie talks about in the section "Performing Custom Query Projections with the Selecting Event" in his post "Using a Custom Linq Expression with the asp:LinqDataSource" control".

oh gosh, i should have mentioned that the REASON i need the intermediary class is because i need to be able to return the result set as a specific type, in this case ProductInfo

List<ProductInfo> products = (from p in db.Products
join c in db.Categories on p.CategoryID = c.ID
select new ProductInfo { ProdName = p.ProdName, ProdID = p.ID,
CategoryID = p.CategoryID, CategoryName = c.CategoryName }).ToList<ProductInfo>();

Related

Dapper question. Getting values from returned object

Just started learning Dapper. I have an ADO.NET background. Using a demo I downloaded, I can insert/delete data from a webform into a MySql table just fine. This, however, I have searched all morning on.
In retrieving a single row from the db by ID, it doesn't return a LIST<>, it seems to be just an object (using code from the demo I downloaded). The query works, I get the object back. It has the fields: "ProductID, Description and Price".
The only way I could get the values to those three fields was like this:
System.Reflection.PropertyInfo pi = Product.GetType().GetProperty("ProductID");
System.Reflection.PropertyInfo desc = Product.GetType().GetProperty("Description");
System.Reflection.PropertyInfo price = Product.GetType().GetProperty("Price");
int _ProductID = (int)(pi.GetValue(Product, null));
string _Description = (string)(desc.GetValue(Product, null));
decimal _Price = (decimal)(price.GetValue(Product, null));
This works and gets the correct values for the three fields.
I'm used to looping through DataTables, but I just think there is probably a better way to get those values.
Is this the correct way to do this or am I missing something? I did actually read documentation and mess with this all morning before asking, too.
Some of the things I looked at seem to be very complex. I thought Dapper was supposed to simplify things.
OK, Thanks Marc. It was difficult for me to see what was supposed to be in the Dapper class files and what was supposed to be in my code behind. The original demo way of getting a product by ID had the query as .FirstOrDefault();
I changed everything to return a List<> and it all worked. I'm sure my ADO.NET is showing, but this works. In Dapper class files:
public List<Product> ProductAsList(int Id)
{
return this._db.Query<Product>("SELECT * FROM Cart_product WHERE ProductID=#Id", new { Id = Id }).**ToList()**;
}
This is just getting one row that matched the ProductID.
In page codebehind:
protected void CartItemAdd(string ProductId) // passing it the selected ProductID
{
var results = cartservice.ProductAsList(Convert.ToInt32(ProductId));
// returns that one row using Dapper ProductAsList(ProductId)
int _ProductId = 0;
string Description = string.Empty;
decimal Price = 0;
// Loop through the list and get the value of each item:
foreach (Product obj in results)
{
_ProductId = obj.ProductID;
Description = obj.Description;
Price = obj.Price;
}
// Using Dapper to insert the selected product into the shopping cart (table):
String UserName = "jbanks";
cartitem = new CartItem();
cartitem.ProductID = _ProductId;
cartitem.Quantity = 1;
cartitem.Description = Description;
cartitem.Price = Price;
cartitem.Created = DateTime.Now;
cartitem.CreatedBy = UserName;
result = cartservice.AddCartItem(cartitem);
if (result)
{
lblMessage.Text = string.Empty;
lblMessage.Text = "Successfully added a cart item";
}
}
}
It does indeed look up the product from one table and insert a selected item into another table.
Thanks again!
The main Query<T> API returns an IEnumerable<T>, which often will be a List<T>; the AsList<T>() extension method can get it back to a list without a copy, but either way: they are just T, for whatever T you asked for. If you asked for Query<Product>, then: they should be Product instances:
var results = connection.Query<Product>(someSql, someArgs); // perhaps .AsList()
foreach (Product obj in results) { // "var obj" would be fine here too
// now just use obj.ProductID, obj.Description and obj.Price
}
If that didn't work: check that you used the <T> version of Query. There is a non-generic variant too, which returns dynamic. Frankly, you should almost always use the <T> version.
Note: I'm assuming that somewhere you have something like
class Product {
public int ProductID {get;set;}
public string Description {get;set;}
public decimal Price {get;set;}
}

Dynamic Linq Library can't handling one on many relationship in select clause

I would like to get records from the database using Dynamic Linq Library NuGet. How can I write query for select parent records along with list of child records. For example there is One-on-Many relationship between Question and Answers table. QuestionID is a Foreign Key column in Answers table.
It is very simple if i give column name in where clause when i am not going to use Dynamic Linq Library NuGet.
var LinQResult=db.Questions
.Include(f=>f.Answers).Where(f=>f.Email=="someEmail").ToList();
I have no question on above query and i can simply render the content on razor views or any web forms.
If i am going to use Dynamic Linq Library NuGet, I have a search criteria in string format like below,
string SearchCreteria = "Email=\"SomeValue\"";
Passing the above dynamic search criteria in where clause below,
var QueryBuilder = (from q in db.Questions
join a in db.Answers on q.QuestionID equals a.QuestionID into answer
from a in answer.DefaultIfEmpty()
select new { q,a}).Distinct().AsQueryable();
var Result = QueryBuilder.Where(SearchCreteria);
How can I convert/add above Result into a strongly typed list below
List<QuestionVM> questionVM=new List<QuestionVM> ();
public class QuestionVM
{
public Question Question { get; set; }
public List<Answer> Answers { get; set; }
}
This should do it I think.
var queryBuilder = (from q in db.Questions.Include(itm => itm.Answers)
join a in db.Answers on q.QuestionID equals a.QuestionID
where a.Email.equals(SearchCriteria)
select q).Distinct().ToList();
var questionVM = new List<QuestionVM>();
foreach(var q in queryBuilder)
{
questionVM.add(new QuestionVM
{
Question = q,
Answers = q.Answers.Where(itm => itm != null).Select(itm => itm.QuestionID == q.ID)
});
}

LINQ, select one item from one table, multiple items from another table

I am fairly new to using LINQ and are now trying to build a LINQ question I do not quite manage to solve.
I would like to ask a question to a database, where I want to bring back single rows from a few tables, but a list of rows from other tables.
See code below too see what I am trying to do:
public DB.store store { get; set; }
public List<DB.gallery_image> images { get; set; }
public List<DB.product> products { get; set; }
public static List<Store> getStoreInfo()
{
DBDataContext db = new DBDataContext();
var _dataToGet = from _store in db.stores
select new Store
{
store = _store,
images = (from a in db.gallery_images
where a.albumID == _store.storeID
select a).ToList(),
products = (from p in db.products
where p.storeID = _store.storeID).ToList()
};
return _dataToGet.ToList();
}
So I just want one row from "store" table, but a list from "images" and "product" tables.
The code above works fine, but is slow as hell.
I don't have any problems to select data from multiple tables as long as there is only one (or none) row per table, but when it is a list I'm having problem...
If I were you I would use deferred execution rather than materializing the queries with a call to ToList. I would change the data type of images and products to IEnumerable<> instead of List<>. Then I would not call ToList in the sub-queries because this results in a roundtrip to the database, hence, depending on how many stores you have it could turn into an extremely slow query.
You should see a performance gain here...
public DB.store store { get; set; }
public IEnumerable<DB.gallery_image> images { get; set; }
public IEnumerable<DB.product> products { get; set; }
public static List<Store> getStoreInfo()
{
DBDataContext db = new DBDataContext();
var _dataToGet = from _store in db.stores
select new Store
{
store = _store,
images = (from a in db.gallery_images
where a.albumID == _store.storeID
select a),
products = (from p in db.products
where p.storeID = _store.storeID)
};
return _dataToGet.ToList();
}

how to query many to many relationship in Linq to EF5

I am learning EF5 and building a small website which simply displays some songs and singers. As a song can be sung by more than one singer and a singer will have many songs so my EF model as below.
I want to display all the list of songs with its relevant singers in a table so I wrote a query and this is so far I have.
Dim res = context.Songs _
.SelectMany(Function(song) song.Artists, Function(s, a) New With
{.SongTitle = s.SongTitle, _
.ArtistName = a.ArtistName, _
.Lyrics = s.Lyrics})
But I am having the result like below.
You will see Lucky is displayed twice in the table. I don't want that to happen. I just want to display it once but have join two singers in the Artist column. I tried to read tutorials and many forum posts but those tutorials don't get this complicated.
So how can i get change the query to return something like this?
I must write my answer with C#, hopefully you are able to translate it into VB.
I would do two changes:
First, simply use Select instead of SelectMany in this situation.
Second, introduce a named ViewModel instead of an anonymous type because it allows you to add a method or custom readonly property that will be helpful later.
The ViewModel would look like this:
public class SongViewModel
{
public string SongTitle { get; set; }
public string Lyrics { get; set; }
public IEnumerable<string> ArtistNames { get; set; }
public string ArtistNamesString
{
get { return string.Join(", ", ArtistNames); }
}
}
Then you can use this query:
var res = context.Songs.Select(s => new SongViewModel
{
SongTitle = s.SongTitle,
Lyrics = s.Lyrics,
ArtistNames = s.Artists.Select(a => a.ArtistName)
});
Now, to list the result, you can use a loop like this (example with console output):
foreach (var item in res)
{
Console.WriteLine(string.Format("{0} {1} {2}",
item.SongTitle, item.Lyrics, item.ArtistNamesString);
}
This lists each song only once and the artist names are displayed as a comma separated list.

Insert record using entity Framework (database first)

there are 3 database tables (movies, reviews, users)
the reviews table include ( MemeberID, MovieID, Review Text, Rate, ReviewDate)
(the MemeberID, and MovieID in the Review are the FK of the members table and the movies table)
The Movie can have many reviews, and i'm trying to add review to a movie
even I have movie class and the member class, I have a problem, in order to insert review, i need to reference it to movie and users , link them, and i don't know how to do it
this code make a error:
" The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects. "
This is my code...
public bool InsertNewReview(Movie _TheMovie, Member _TheMember, string _Text, byte _Rate, DateTime _ReviewDate)
{
Review ReviewToInsert = new Review()
{
MovieID = _TheMovie.MovieID,
MemberID = _TheMember.MemberID,
Movie = _TheMovie,
Member = _TheMember,
Rate = _Rate,
ReviewDate = _ReviewDate,
ReviewText = _Text
};
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
return true;
}
..
there are more data to insert to the Review class
Images: here
..
and the tables: (the "all columns" isn't a field in database tables)
Images: here
could you try like this
Review ReviewToInsert = videoLib.Reviews.CreateObject();
ReviewToInsert.MovieID = _TheMovie.MovieID
...
...
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
I got a solution, I need to define only the MovieID, MemberID, and not using their object
and use try & catch, to detect if thier the same MovieID (fk) and MemberID (fk) in the same row (because the review don't have is own id in the database)
public bool InsertNewReview(string _MovieID, int _MemberID, string _Text, byte _Rate, DateTime _ReviewDate)
{
try
{
Review ReviewToInsert = new Review()
{
Rate = _Rate,
ReviewDate = _ReviewDate,
ReviewText = _Text,
MovieID = _MovieID,
MemberID = _MemberID
};
videoLib.Reviews.AddObject(ReviewToInsert);
videoLib.SaveChanges();
return true;
}
catch
{
return false;
}
}

Resources