I'm using Dapper and I have classes like this:
public class Article{
public int Id { get; set; }
public string Description{get;set;}
public Group Group { get; set; }
public List<Barcode> Barcode {get;set;}
...
}
public class Group{
public int Id { get; set; }
public string Description {get;set;}
}
public class Barcode{
public int Id { get; set; }
public string Code{get;set;}
public int IdArticle { get; set; }
...
}
I can get all information about Article but I would like to know if is possible with one query get also the list of barcodes for each article. Actually what I do is this:
string query = "SELECT * FROM Article a " +
"LEFT JOIN Groups g ON a.IdGroup = g.Id ";
arts = connection.Query<Article, Group, Article>(query,
(art, gr) =>
{ art.Group = gr; return art; }
, null, transaction).AsList();
I also found a good explanation here but I don't understand how to use it in my case, because I have also the Group class.
How should I do this with Dapper, is it possible or the only way is to do different steps?
Thanks
QueryMultiple is your friend
var query = #"
select a.*, g.* from Article a left join Groups g on g.Id = a.IdGroup
select * from Barcode";
//NOTE: IdGroup should exists in your Article class.
IEnumerable<Article> articles = null;
using (var multi = connection.QueryMultiple(query)){
articles = multi.Read<Article, Group, Article>((a, g)=>
{ a.Group = g; return a; });
if (articles != null) {
var barcodes = multi.Read<Barcode>().ToList();
foreach(var article in articles){
article.Barcode = barcodes.Where(x=>x.IdArticle == article.Id).ToList();
}
}
}
That may not be fun especially if you don't have any filters in your query. But I doubt that you will return all Articles. In that case you can filter the Barcode like this (edited sql) > select * from Barcode where Id in #ids. Then include the parameter ids (a list of Article Ids) in the QueryMultiple.
Option2
Or you could just do separate queries:
var query = "select a.*, g.* from Article a left join Groups g on g.Id = a.IdGroup";
var articles = connection.Query<Article, Group, Article>(query,
(a,g)=> { a.Group = g; return g; }).ToList();
query = "select * from Barcode where IdArticle IN #articleIds";
var articleIds = articles.Select(x=>x.Id);
var barcodes = connection.Query<Barcode>(query, new { articleIds });
foreach(var article in articles){
article.Barcode = barcodes.Where(x=>x.IdArticle == article.Id);
}
I prefer the first option.
Related
I'm trying to build a lambda expression to get the grand total but I'm still struggling to achieve the desired result. I've managed to achieve the same using SQL and LINQ using joints but it would be great if someone could give me a hand to re-write the query using lambda and navigation properties (without joints).
SQL Query:
SELECT SUM(a.[Quantity] * (a.[Price] + b.[ExtraValue])) + SUM(d.SubMealTotal * a.[Quantity]) AS [Total]
FROM [dbo].[OrderedMeals] a
INNER JOIN [dbo].[OrderedMealPortions] b
ON a.Id = b.[OrderedMealId]
LEFT OUTER JOIN (SELECT OrderedMealId, Sum(Price) AS SubMealTotal FROM [dbo].[OrderedSubMeals]
GROUP BY OrderedMealId) AS d
ON a.Id = d.[OrderedMealId]
WHERE a.[Quantity] > 0
Then the LINQ - Please let me know if I'm missing something here or there is a better way:
(from orderedMeal in _context.OrderedMeals.Where(x => x.Quantity > 0)
join orderedMealPortion in _context.OrderedMealPortions
on orderedMeal.Id equals orderedMealPortion.OrderedMealId
join orderedSubMeal in _context.OrderedSubMeals
on orderedMeal.Id equals orderedSubMeal.OrderedMealId into gs
from subOrderedSubMeal in gs.DefaultIfEmpty()
group subOrderedSubMeal by new { subOrderedSubMeal.OrderedMealId, orderedMeal.Price, orderedMeal.Quantity, orderedMealPortion.ExtraValue } into g
select new
{
MealTotal = (g.Key.ExtraValue + g.Key.Price) * g.Key.Quantity + g.Sum(x => x.Price * g.Key.Quantity),
}).Sum(x => x.MealTotal); // Not sure how to get the sum using LINQ
Entities:
public class OrderedMeal
{
public int Id { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public int OrderedMealPortionId { get; set; }
public OrderedMealPortion? OrderedMealPortion { get; set; }
public ICollection<OrderedSubMeal>? OrderedSubMeals { get; set; }
}
public class OrderedMealPortion
{
public int Id { get; set; }
public int OrderedMealId { get; set; }
public OrderedMeal? OrderedMeal { get; set; }
public decimal? ExtraValue { get; set; }
}
public class OrderedSubMeal
{
public int Id { get; set; }
public int OrderedMealId { get; set; }
public OrderedMeal? OrderedMeal { get; set; }
public decimal Price { get; set; }
}
I can't test with a database, but I think this implements the query logic and will produce the same result:
var ans = OrderedMeals
.Where(om => om.Quantity > 0 && om.OrderedMealPortion != null)
.Sum(om => om.Quantity * (om.Price +
om.OrderedMealPortion!.ExtraValue +
(om.OrderedSubMeals != null ? om.OrderedSubMeals.Sum(osm => osm.Price) : 0)) );
This is direct translation from the SQL:
var groupingQuery =
from sm in _context.OrderedSubMeals
group sm by new { sm.OrderedMealId } into g
select new
{
g.Key.OrderedMealId,
SubMealTotal = g.Sum(x => x.Price)
};
var query =
from om in _context.OrderedMeals
join g in groupingQuery on om.Id equals g.OrderedMealId into gj
from g in gj.DefaultIfEmpty()
where om.Quantity > 0
select new { om, om.OrderedMealPortion, g };
var result = query.Sum(x => x.om.Quantity * (x.om.Price + x.OrderedMealPortion.ExtraValue + x.g.SubMealTotal));
But I have feeling that query can be simplified without grouping.
I'm sure someone else has asked this but I searched on what I could think of to find the solution.
I've got the following data models to match tables in my SQL db:
public class ProfileDetailModel
{
public string id { get; set; }
public string name { get; set; }
public StyleList[] styleList { get; set; }
public FabricList[] fabricList { get; set; }
}
public class StyleList
{
public string id { get; set; }
public string name { get; set; }
}
public class FabricList
{
public string id { get; set; }
public string fabricName { get; set; }
}
This is the current query code:
var query = (from t in db.tblProfiles
select new ProfileDetailModel()
{
id = t.id,
name = t.name
});
var querylist = await query.ToListAsync();
(prototyped linq queries below for style and fabric)
var styleQuery = (from t in db.tblStyles
select new styleList()
{
id = t.id,
name = t.name
});
var fabricQuery = (from t in db.tblFabrics
select new fabricList()
{
id = t.id,
name = t.name
});
if (queryList.Count > 0)
{
var item = queryList[0];
item.styleList = styleQuery;
item.fabricList = fabricQuery;
}
I'll have one profileDetailModel with multiple items in styleList and in fabricList. EG.
ProfileDetailModel
Data: Pants
styleList: Bell Bottom, Straight Leg, Boot fit
fabricList: jean-blue, jean-black, plaid
All three above models are tables in my db. I could issue 3 separate queries to read the data then assemble after the fact. But is there a way I can do a linq query to include the two arrays in the main query in one shot?
Try this:
var newQuery = (from p in db.tblProfiles
select p)
.AsEnumerable()
.Select(x => new ProfileDetailModel()
{
id = x.id,
name = x.name,
styleList = styleQuery,
fabricList = fabricQuery
});
i have basically a post repository that should return all the gallery items belong to it. If there's no gallery belonging to post it should still return post distinct by post id
public List<PostLocalizedOutput> GetAllPostsWithCategories(string culture, bool? isPublished)
{
var query =
from p in Context.Posts
join pl in Context.PostsLocalized on p.Id equals pl.PostId
from c in p.Categories
join cl in Context.CategoriesLocalized on c.Id equals cl.CategoryId
from g in p.Galleries.DefaultIfEmpty()
join gi in Context.GalleryItems on g.Id equals gi.GalleryId
where
pl.Culture == culture &&
cl.Culture == culture
select new PostLocalizedOutput
{
PostId = pl.PostId,
CategoryId = cl.CategoryId,
Title = pl.Title,
FormattedCategoryName = cl.FormattedCategoryName,
PostContent = pl.PostContent,
PostType = pl.Post.PostType,
IOrder = pl.Post.IOrder,
Tags = pl.Tags,
PublishDate = pl.Post.PublishDate,
ViewCount = pl.Post.ViewCount,
ShowInHomePageSlider = pl.Post.ShowInHomePageSlider,
AllowComments = pl.Post.AllowComments,
Image = pl.Post.Image,
IsArchived = pl.Post.IsArchived,
IsDraft = pl.Post.IsDraft,
IsPublished = pl.Post.IsPublished,
GalleryItems = new GalleryItemOutput
{
FileName = gi.FileName,
GalleryId = gi.GalleryId,
Id = gi.Id,
Notes = gi.Notes,
Title = gi.Title
} (around here i feel like i should foreach something or what?)
};
return query.OrderBy(x => x.IOrder).ThenBy(x => x.PublishDate).DistinctBy(x => x.PostId).ToList();
}
here is my postlocalizedoutput
public class PostLocalizedOutput : IOutputDto
{
public int PostId { get; set; }
public int CategoryId { get; set; }
public bool IsPublished { get; set; }
...
public List<GalleryItemOutput> GalleryItems { get; set; }
}
GalleryItemOutput should be list because i want all the galleryitems of a post. But when i define it as a list in repository i cannot set each field of galleryitem of a post. This code now returns me four rows because i have four gallery items of that post and each one has the same postId. I do not want that. DefaultIfEmpty also does not work even if a post does not have any gallery items i should still be able to get that post without gallery items.
Any approach ?
Thanks for all suggestions.
This should help you get started.
Don’t know if this compiles, please just interpret as pseudo-code. Looks like your top-level range variable is PostsLocalized not Posts. This code assume/uses navigation properties that are probably setup in your EDM classes. I have overused let keyword here just to make it clearer for you. In the select new clause you can change the p1.Post.... to just p. I left them to make as few edits as possible.
var query =
from pl in Context.PostsLocalized
where pl.Culture == culture
let p = p1.Post
let categories = p.Categories
let localizedCategories = categories.SelectMany(cat => cat.CategoriesLocalized).Where(cl => cl.Culture == culture)
let galleries = p.Galleries
let galleryItems = galleries.SelectMany(gal => gal.GalleryItems)
let cl = localizedCategories.FirstOrDefault() // only one or zero of these i assume?
select new PostLocalizedOutput
{
PostId = p1.PostId,
CategoryId = cl.CategoryId,
Title = pl.Title,
FormattedCategoryName = cl.FormattedCategoryName,
PostContent = pl.PostContent,
PostType = pl.Post.PostType,
IOrder = pl.Post.IOrder,
Tags = pl.Tags,
PublishDate = pl.Post.PublishDate,
ViewCount = pl.Post.ViewCount,
ShowInHomePageSlider = pl.Post.ShowInHomePageSlider,
AllowComments = pl.Post.AllowComments,
Image = pl.Post.Image,
IsArchived = pl.Post.IsArchived,
IsDraft = pl.Post.IsDraft,
IsPublished = pl.Post.IsPublished,
GalleryItems = galleryItems.Select(gi => new GalleryItemOutput
{
FileName = gi.FileName,
GalleryId = gi.GalleryId,
Id = gi.Id,
Notes = gi.Notes,
Title = gi.Title
})
// might need a .ToList() here on those GalleryItems
(around here i feel like i should foreach something or what?)
};
I keep getting the error below on my code, and can't understand why it is having problems translating it to a query, it is pretty simple.
I have 2 repositories, Album and AlbumImage, when I fetch an album do I want a cover, that is a subselect in AlbumImages. What am I doing wrong here?
LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[Sogaard.us.Cosplay.Data.AlbumImage] Get()'
method, and this method cannot be translated into a store expression.
Album repository
public class AlbumRepository : IRepository<Album>
{
private CosplayEntities _entities;
private IRepository<AlbumImage> _imageRepository;
public AlbumRepository(CosplayEntities entities, IRepository<AlbumImage> imageRepository)
{
_entities = entities;
_imageRepository = imageRepository;
}
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _imageRepository.Get() where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select ai).FirstOrDefault(),
});
}
}
AlbumImage repository
public class AlbumImageRepository : IRepository<AlbumImage>
{
private CosplayEntities _entities;
public AlbumImageRepository(CosplayEntities entities)
{
_entities = entities;
}
public IQueryable<AlbumImage> Get()
{
return (from ai in _entities.AlbumImages
select new AlbumImage()
{
Id = ai.Id,
AlbumId = ai.AlbumId,
UserId = ai.UserId,
Type = ai.Type,
Width = ai.Width,
Height = ai.Height,
Description = ai.Description,
Views = ai.Views,
Uploadet = ai.Uploadet,
LastView = ai.LastView,
Thumblink = ai.Thumblink,
Imagelink = ai.Imagelink,
Cover = ai.Cover
});
}
This is the code i am getting the error on
_albumImageRepository = new AlbumImageRepository(_entities);
_albumRepository = new AlbumRepository(_entities, _albumImageRepository);
_albumImagesTagRepository = new AlbumImagesTagRepository(_entities);
....
var album = _albumRepository.Get().Where(x => x.Id == image.AlbumId).FirstOrDefault();
Update: I have commented the Cover = ... out in my IQueryable Get() so it is 2 simple select as object.
And i still get the error in something as simple as
model.Albums = (from a in _albumRepository.Get()
orderby a.Id descending
select new AlbumDisplayModel()
{
Album = a,
ImageCount = _albumImageRepository.Get().Where(x => x.AlbumId == a.Id).Count(),
User = _userRepository.Get().Where(x => x.Id == a.UserId).FirstOrDefault()
})
.Skip(AlbumsPrPage * (page - 1))
.Take(AlbumsPrPage).ToList();
Update 2: If i rewrite the IQueryable Get() to the following, do it work flawlessly, there there should really be no diffrence in how it is handled?
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _entities.AlbumImages where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select new AlbumImage()
{
Id = ai.Id,
AlbumId = ai.AlbumId,
UserId = ai.UserId,
Type = ai.Type,
Width = ai.Width,
Height = ai.Height,
Description = ai.Description,
Views = ai.Views,
Uploadet = ai.Uploadet,
LastView = ai.LastView,
Thumblink = ai.Thumblink,
Imagelink = ai.Imagelink,
Cover = ai.Cover
}).FirstOrDefault(),
});
}
Update 3: Did a little test, and the problem seems to be with Entity framework, se the following code, The var linqAlbum = testClass.LinqAlbumGet().ToList(); executes without any problems and return the correct data, var eeAlbum = testClass.EEAlbumGet().ToList(); fails with the exception
LINQ to Entities does not recognize the method
'System.Linq.IQueryable`1[RepositoryTest.TestAlbumCover] EEImageGet()'
method, and this method cannot be translated into a store expression.
My test script
class Program
{
static void Main(string[] args)
{
var linq = new LinqDataContext();
var ee = new NewCosplayEntities();
var testClass = new Test(linq, ee);
var linqAlbum = testClass.LinqAlbumGet().ToList();
var eeAlbum = testClass.EEAlbumGet().ToList();
}
}
public class Test
{
public NewCosplayEntities ee { get; set; }
public LinqDataContext linq { get; set; }
public Test(LinqDataContext linq, NewCosplayEntities ee)
{
this.linq = linq;
this.ee = ee;
}
public IQueryable<TestAlbum> LinqAlbumGet()
{
return from a in linq.Albums
select new TestAlbum
{
Id = a.Id,
Name = a.Name,
Cover = (from i in LinqImageGet() where i.AlbumId == a.Id select i).FirstOrDefault()
};
}
public IQueryable<TestAlbumCover> LinqImageGet()
{
return from i in linq.AlbumImages
select new TestAlbumCover()
{
Id = i.Id,
AlbumId = i.AlbumId
};
}
public IQueryable<TestAlbum> EEAlbumGet()
{
return from a in ee.Albums
select new TestAlbum
{
Id = a.Id,
Name = a.Name,
Cover = (from i in EEImageGet() where i.AlbumId == a.Id select i).FirstOrDefault()
};
}
public IQueryable<TestAlbumCover> EEImageGet()
{
return from i in ee.AlbumImages
select new TestAlbumCover()
{
Id = i.Id,
AlbumId = i.AlbumId
};
}
}
public class TestAlbum
{
public int Id { get; set; }
public string Name { get; set; }
public TestAlbumCover Cover { get; set; }
}
public class TestAlbumCover
{
public int Id { get; set; }
public int AlbumId { get; set; }
}
Your problem comes in the ItemRepository for Albumn. Specifically because _entities has no knowledge of the _imageRepository type, so it doesn't know how to translate that type into the appropriate TSQL script. You could cast the _entities.Albums.ToList() to force the IQueryable into an IEnumerable before you try to access the _ImageRepository.Get() from the scope of the hydrated object instead of directly on the database instance. Realize that you are then going to see a perf hit on the n+1 database requests for the AlbumImage child objects for each Album.
public IQueryable<Album> Get()
{
return (from a in _entities.Albums
select new Album()
{
Id = a.Id,
UserId = a.UserId,
Name = a.Name,
Created = a.Created,
LastEdit = a.LastEdit,
Description = a.Description,
Views = a.Views,
Location = a.Location,
Photoshoot = a.Photoshoot,
Cover = (from ai in _imageRepository.Get() where ai.AlbumId == a.Id orderby ai.Cover descending, ai.Id ascending select ai).FirstOrDefault(),
});
}
Ultimately, the problem is that your trying to use an ActiveRecord pattern rather than a true repository. Everything in a single IQueryable needs to be fetched through the same database context instance for parsing and tracking purposes.
Potentially its because you are wrapping the Album and AlbumImage in new references. I would remove that and do the projection after your query.
I don't think you can project into an entity and have each projection use a result from another IQueryable. If you replaced the contents of IQueryable<AlbumImage> Get() with this, it might work:
from a in _entities.Albums
join c in _imageRepository.Get() on a.Id equals c.AlbumId into acJoin
from ac in acJoin.DefaultIfEmpty()
select new Album()
{
Id = a.Id,
etc..,
etc..,
Cover = ac
}
I'm actually fairly certain that you will need to adjust this freehand query, but essentially it's joining the IQueryables, then projecting those results into your objects, instead of projecting to your objects then inserting an IQueryable into those results. Not the best explanation I know, but just look up "LINQ Left Join" or "Linq Left Outer Join" to see the syntax of what I'm describing here. Example
what I am doing here now is the probably the worst way of fulfilling my requirements but I haven't found any other way.
here is my sample database structure;
Here is the script that I use in order to retrieve the certain values;
SELECT DISTINCT h.HotelID, h.HotelName, r.RoomCode, r.RoomName, r.RoomID
FROM RoomsInHotel rh
INNER JOIN Hotels h ON rh.HotelID = h.HotelID
INNER JOIN Rooms r ON rh.RoomID = r.RoomID
order by h.HotelName, r.RoomCode;
Here is the result that the above script is giving me back;
everything is fine till here.
I need to move to C# code from here. What I would like to achieve is the following result;
Here is where I am worried about. I use Linq to achieve this thing and the below code is the code that I used for the above console result.
public class Hotel {
public int HotelID {get; set; }
public string HotelName {get; set; }
public IQueryable<Room> Rooms {get; set; }
}
public class HotelWithOneRoom {
public int HotelID { get; set; }
public string HotelName { get; set; }
public Room Room { get; set; }
}
public class Room {
public int RoomID {get; set; }
public string RoomCode {get; set; }
public string RoomName { get; set; }
}
class Program {
static void Main(string[] args) {
#region _assets
IList<HotelWithOneRoom> tempHotelWithOneRoom = new List<HotelWithOneRoom>();
IList<Hotel> tempDistinctHotels = new List<Hotel>();
IList<Room> tempRooms = new List<Room>();
#endregion
#region _connectionString
var connectionString = "Data Source=TOSHIBA-PC\\SQLEXPRESS;Initial Catalog=tbAccomm;Integrated Security=True";
#endregion
using (SqlConnection conn = new SqlConnection(connectionString)) {
using(SqlCommand cmd = conn.CreateCommand()) {
#region _connect to db, generate script and retrieve values
cmd.CommandText = "SELECT DISTINCT h.HotelID, h.HotelName, r.RoomCode, r.RoomName, r.RoomID FROM RoomsInHotel rh INNER JOIN Hotels h ON rh.HotelID = h.HotelID INNER JOIN Rooms r ON rh.RoomID = r.RoomID order by h.HotelName, r.RoomCode;";
cmd.CommandType = System.Data.CommandType.Text;
conn.Open();
SqlDataReader r = cmd.ExecuteReader();
#endregion
#region _assigning the values to tempHotelWithOneRoom
while (r.Read()) {
tempHotelWithOneRoom.Add(new HotelWithOneRoom {
HotelID = int.Parse(r["HotelID"].ToString()),
HotelName = r["HotelName"].ToString(),
Room = new Room {
RoomID = int.Parse(r["RoomID"].ToString()),
RoomCode = r["RoomCode"].ToString(),
RoomName = r["RoomName"].ToString()
}
});
}
#endregion
foreach (var item in tempHotelWithOneRoom) {
if (tempDistinctHotels.Where(x => x.HotelID == item.HotelID).Count() < 1) {
tempDistinctHotels.Add(new Hotel {
HotelID = item.HotelID,
HotelName = item.HotelName
});
var _tempHotel = tempDistinctHotels.Single(x => x.HotelID == item.HotelID);
var _tempRoomList = new List<Room>();
if (_tempHotel.Rooms != null) {
foreach (var _item in _tempHotel.Rooms) {
_tempRoomList.Add(_item);
}
}
_tempRoomList.Add( new Room {
RoomCode = item.Room.RoomCode,
RoomID = item.Room.RoomID,
RoomName = item.Room.RoomName
});
_tempHotel.Rooms = _tempRoomList.AsQueryable();
} else {
var _tempHotel = tempDistinctHotels.Single(x => x.HotelID == item.HotelID);
var _tempRoomList = new List<Room>();
if (_tempHotel.Rooms != null) {
foreach (var _item in _tempHotel.Rooms) {
_tempRoomList.Add(_item);
}
}
_tempRoomList.Add( new Room {
RoomCode = item.Room.RoomCode,
RoomID = item.Room.RoomID,
RoomName = item.Room.RoomName
});
_tempHotel.Rooms = _tempRoomList.AsQueryable();
}
}
#region _output the result
foreach (var item in tempDistinctHotels) {
Console.WriteLine(
"Hotel Name : " + item.HotelName + ", " + "Room Count : " + item.Rooms.Count()
);
foreach (var item2 in item.Rooms) {
Console.WriteLine("--" + item2.RoomCode + ", " + item2.RoomName);
}
}
#endregion
r.Close();
Console.Read();
}
}
}
}
IMO, if there was a competition on the worst c# code, I would be winning that competition with this code. (Would I?)
So, what is the most optimized way of doing what I do?
C# is not my language of choice but here you go:
Dictionary<int, Hotel> Hotels = new Dictionary<int, Hotel> ();
while (r.Read()) {
if (!Hotels.ContainsKey(r["HotelID"])) {
NewHotel Hotel= new Hotel();
NewHotel.HotelID = r["HotelID"];
Newhotel.HotelName = r["HotelName"];
NewHotel.Rooms = new Dictionary<int, Room> ();
Hotels.Add(NewHotel);
}
Room NewRoom = new Room();
NewRoom.RoomID = r["RoomName"];
NewRoom.RoomCode = r["RoomCode"];
NewRoom.RoomName = r["RoomName"];
Hotels.Items("HotelID").Rooms.Add(NewRoom);
}
Like jpmcclung pointed out, you'll need some Software engineering Skills to create successful Applications. The bigger your Project, the more design and planning is called for.
The best way to get around writing code like this is to study up on the practice of test driven design. This code is screaming for it. To see it in action I would check out Brad Wilson's new TDD Full Throttle video at TekPub (http://shop.tekpub.com/products/ft_tdd_wilson) it is 12 bucks but it will be worth it. Otherwise there are numerous resourses on the subject.
Specifically, why do you need a HotelWithOneRoom? Just add one room to the Rooms list in a regular hotel. Why don't you override .ToString() on the Hotel and use the StringBuilder to create the output line for a hotel? Those are just a few things off the top of my head but if you use TDD it will help organize your design practice and get some of this code out of your way.
Since you include IQueryable in your example, can we assume that LINQ to SQL or EF is an option for your solution? If so, realize that they support projecting into object heirarchies directly.
Asssuming you have associations set between your tables, it could be as simple as:
var query = from hotel in context.hotels
select new Hotel { HotelID = hotel.HotelID,
HotelName = hotel.HotelName,
Rooms = (from room in hotel.Rooms
select new Room {
RoomID = room.RoomID,
RoomCode = room.RoomCode,
RoomName = room.RoomName })
.Distinct()
};
I think you could start from the beginning here and avoid some confusion by renaming your Tables. I think your table names should be Hotel, Room and RoomType (I'm not a fan of pluralized table names, but that's beside the point).
To think in 'Domain' terms, you have a Hotel. A hotel has Rooms. Each room is defined as a type of room, Double, Single, etc...
Anyway, I threw some code together that's doing the same thing yours is doing. It's a little clearer I think.
For database access, I used Massive https://github.com/robconery/massive because it's quick and fun.
Anyway, here is the code I came up with.
class Program {
static void Main(string[] args) {
const string sqlStmnt = #"SELECT h.HotelID, h.HotelName, r.HotelRoomID, rt.RoomTypeCode, rt.RoomTypeName FROM Hotel h INNER JOIN HotelRoom r ON r.HotelID = h.HotelID INNER JOIN RoomType rt ON r.RoomTypeID = rt.RoomTypeID order by h.HotelName, rt.RoomTypeCode";
var context = new HotelContext();
var hotelData = context.Query(sqlStmnt);
var hotelList = new List<Hotel>();
//Load our objects
foreach (dynamic data in hotelData) {
int hotelID = data.HotelID;
var hotel = hotelList.Where(h => h.HotelID == hotelID).FirstOrDefault()
?? new Hotel() {HotelName = data.HotelName};
hotel.AddRoom(new HotelRoom { HotelRoomID = data.HotelRoomID, RoomType = new RoomType{ TypeCode = data.RoomTypeCode, TypeDescription = data.RoomTypeName}});
if (hotel.HotelID != 0) {continue;}
hotel.HotelID = hotelID;
hotelList.Add(hotel);
}
//Display our output
foreach (var hotel in hotelList) {
Console.WriteLine("Hotel Name : " + hotel.HotelName + ", Room Count : " + hotel.HotelRooms.Count());
foreach (var room in hotel.HotelRooms) {
Console.WriteLine("--" + room.RoomType.TypeCode + ", " + room.RoomType.TypeDescription);
}
}
Console.ReadLine();
}
}
Here is my Database stuff.
public class HotelContext : DynamicModel {
public HotelContext():base("test") {
PrimaryKeyField = "HotelID";
TableName = "Hotel";
}
}
Here are the classes I used. Couldn't ever figure out what your HotelWithOneRoom was meant for.
public class Hotel{
private readonly List<HotelRoom> _rooms = new List<HotelRoom>();
public int HotelID { get; set; }
public string HotelName { get; set; }
public void AddRoom(HotelRoom room) {_rooms.Add(room);}
public IQueryable<HotelRoom> HotelRooms {get {return _rooms.AsQueryable();}}
}
public class HotelRoom {
public int HotelRoomID { get; set; }
public RoomType RoomType { get; set; }
}
public class RoomType {
public string TypeCode { get; set; }
public string TypeDescription { get; set; }
}