At the moment, I have multiple tables in my Database with slightly varying columns to define different "history" elements for an item.
So I have my item table;
int ItemId {get;set}
string Name {get;set}
Location Loc {get;set}
int Quantity {get;set}
I can do a few things to these items like Move, Increase Quantity, Decrease Quantity, Book to a Customer, "Pick" an item, things like that. So I have made multiple "History Tables" as they have different values to save E.g
public class MoveHistory
{
public int MoveHistoryId { get; set; }
public DateTime Date { get; set; }
public Item Item { get; set; }
public virtual Location Location1Id { get; set; }
public virtual Location Location2Id { get; set; }
}
public class PickingHistory
{
public int PickingHistoryId { get; set; }
public DateTime Date { get; set; }
public Item Item { get; set; }
public int WorksOrderCode { get; set; }
}
This is fine apart from where I want to show a complete history for an item displayed in a list;
Item 123 was moved on 23/02/2013 from Location1 to Location2
Item 123 was picked on 24/02/2013 from work order 421
I am using Entity Framework, .NET 4.5, WPF, and querying using Linq but cannot figure a way of taking these lists of history elements, and ordering them out one by one based on their date.
I can think of messy ways, like one single history table with columns used if required. Or even create a third list containing the date and what list it came from, then cycle through that list picking the corresponding contents from the corresponding list. However, I feel there must be a better way!
Any help would be appreciated.
If you implement a GetDescription() method on your history items (even as an extension method), you can do this:
db.PickingHistory.Where(ph => ph.Item.ItemId == 123)
.Select(ph => new { Time = ph.Date, Description = ph.GetDescription() })
.Concat(db.MoveHistory.Where(mh => mh.ItemId == 123)
.Select(mh => new { Time = mh.Date, Description = mh.GetDescription() })
.OrderByDescending(e => e.Time).Select(e => e.Description);
The problem you are facing is that you're trying to use your database model as a display model and obviously are failing. You need to create a new class that represents your history grid and then populate it from your various queries. From your example output the display model may be:
public class HistoryRow{
public DateTime EventDate { get; set; }
public string ItemName { get; set; }
public string Action { get; set; }
public string Detail { get; set; }
}
You then load the data into this display model:
var historyRows = new List<HistoryRow>();
var pickingRows = _db.PickingHistory.Select(ph => new HistoryRow{
EventDate = ph.Date,
ItemName = ph.Item.Name,
Action = "picked",
Detail = "from works order " + ph.WorksOrderCode);
historyRows.AddRange(pickingRows);
var movingRows = _db.MoveHistory.Select(mh => new HistoryRow{
EventDate = mh.Date,
ItemName = ph.Item.Name,
Action = "moved",
Detail = "from location " + mh.Location1Id + " to location " + mh.Location2Id);
historyRows.AddRange(movingRows );
You can repeatedly add the rows from various tables to get a big list of the HistoryRow actions and then order that list and display the values as you wish.
foreach(var historyRow in historyRows)
{
var rowAsString = historyRow.ItemName + " was " + historyRow.Action.....;
Console.WriteLine(rowAsString);
}
If you are implementing this in order to provide some sort of undo/redo history, then I think that you're going about it in the wrong way. Normally, you would have one collection of ICommand objects with associated parameter values, eg. you store the operations that have occurred. You would then be able to filter this collection for each item individually.
If you're not trying to implement some sort of undo/redo history, then I have misunderstood your question and you can ignore this.
Related
public static List<TruckWithModel> GetAllTrucks()
{
using (DAD_BaldipContext ctx = new DAD_BaldipContext())
{
var x = ctx.TruckFeatureAssociation
.Include(t => t.Truck)
.Include(tf => tf.Feature)
.Include(tm => tm.Truck.TruckModel)
.Select(it => new TruckWithModel()
{
Colour = it.Truck.Colour,
Size = it.Truck.TruckModel.Size,
RentalPrice = it.Truck.DailyRentalPrice,
Status = it.Truck.Status,
Model = it.Truck.TruckModel.Model,
Rego = it.Truck.RegistrationNumber,
Features = it.Feature.Description
}) ;
return (List<TruckWithModel>)x.ToList();
}
}
This code retrieves the various attribute values from the relative tables TruckFeatureAssociation, TruckFeature, IndividualTruck and TruckModel.
The trouble I'm having is that the TruckFeatureAssociation has up to 5 entries for the same truck, this table is a junction table between IndividualTruck and TruckFeature where TruckFeature is a table of various features.
For each TruckFeatureAssociation a different object of TruckWithModel is created i.e. if there are 3 features associated each truck has three rows displayed in the datagrid where I call this function.
I want it so that all the features can be stored in one object.
So in the above output I would want, only one row, saying alarm systems, chrome wheels.
The issue here is that you are querying against Features, but the model reflects a Truck... Query a truck, get it's features, then let your view model (TruckWithModel) help format that data for the view..
For example:
[Serializable]
public class TruckWithModel
{
public string Colour { get; set; }
public string Size { get; set; }
public decimal RentalPrice { get; set; }
public string Status { get; set; }
public string Model { get; set; }
public List<string> Features { get; set; } = new List<string>();
public string FormattedFeatures
{
get { return string.Join(", ", Features); }
}
}
Now when you query the data:
var trucks = ctx.Trucks
.Select(t => new TruckWithModel()
{
Colour = t.Colour,
Size = t.TruckModel.Size,
RentalPrice = t.DailyRentalPrice,
Status = t.Status,
Model = t.TruckModel.Model,
Rego = t.RegistrationNumber,
Features = t.Features.Select(f => f.Description).ToList()
}).ToList();
This assumes that Truck has a Collection of Features where TruckFeatureAssociation is just a mapping entity. If your Truck's collection is based off TruckFeatureAssociation:
Features = t.Features.Select(f => f.Feature.Description).ToList()
Now in your view, where you want to display the features, bind to the FormattedFeatures property to get the comma-delimited list of Features for each Truck.
Note that when you use Projection through .Select() you do not need to use .Include() to eager load related entities. EF can work out what to load to satisfy the projection automatically.
In Example II of Indexing Related Documents, an index is built over Authors by Name and Book title. The relevant entities look like so:
public class Book {
public string Id { get; set; }
public string Name { get; set; }
}
public class Author {
public string Id { get; set; }
public string Name { get; set; }
public IList<string> BookIds { get; set; }
}
I.e. only the Author holds information about the relation. This information is used in constructing said index.
But how would I construct an index over Books by Authors (assuming a book could have multiple authors)?
Edit:
The book/author analogy only goes so far. I'll make an example that's closer to my actual use case:
Suppose we have some tasks that are tied to locations:
public class Location {
public string Id { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}
public class Task {
public string Id { get; set; }
public string Name { get; set; }
public string LocationId { get; set; }
public Status TaskStatus { get; set; }
}
I have an endpoint serving Locations as GeoJson to a map view in a client. I want to color the Locations depending on status of Tasks associated with them. The map would typically show 500-2000 locations.
The query on locations is implemented as a streaming query.
Using the query-method indicated in Ayende's initial answer, I might do something like:
foreach (var location in locationsInView)
{
var completedTaskIds = await RavenSession.Query<Task>()
.Where(t => t.LocationId == location.Id && t.TaskStatus == Status.Completed)
.ToListAsync();
//
// Construct geoJson from location and completedTaskIds
//
}
This results in 500-2000 queries being executed against RavenDB, which doesn't seem right.
This is why I initially thought I needed an index to construct my result.
I have since read that RavenDB caches everything by default, so that might be a non-issue. On the other hand, having implemented this approach, I get an error ("...maximum number of requests (30) allowed for this session...").
What is a good way of fixing this?
You cannot index them in this manner.
But you also don't need to.
If you want to find all the books by an author, you load the author and you have the full list.
You can do this using a multi map/reduce index.
All sources of truth about the objects of interest are mapped to a common object type (Result). This mapping is then reduced grouping by Id and keeping just the relevant pieces, creating a "merge" of truths about each object (Book in this case). So using the Book/Author example, where several Authors might have contributed to the same book, you could do something like the following.
Note that the map and reduce steps must output the same type of object, which is why author.Id is wrapped in a list during the mapping from author.
Author.Names are excluded for brevity, but could be included in the exact same way as Author.Id.
public class BooksWithAuthors : AbstractMultiMapIndexCreationTask<BooksWithAuthors.Result>
{
public class Result
{
string Id;
string Title;
IEnumerable<string> AuthorIds;
}
public BooksWithAuthors()
{
AddMap<Book>(book => from book in books
select new
{
Id = book.Id,
Title = book.Title,
AuthorIds = null;
});
AddMap<Author>(author => from author in authors
from bookId in author.bookIds
select new
{
Id = bookId,
Title = null,
AuthorIds = new List<string>(){ author.Id };
});
Reduce = results => from result in results
group result by result.Id
into g
select new
{
Id = g.Key,
Title = g.Select(r => r.Title).Where(t => t != null).First(),
AuthorIds = g.Where(r => r.AuthorIds != null).SelectMany(r => r.AuthorIds)
};
}
}
I have the code below which works:
List<XXTJobTableModel> xjobs = filexxts.GroupBy(x=> x.job)
.Select(fx => new JobTableModel
{
job_no = fx.First().job,
emps = fx.GroupBy(x=>x.emp_id).Select(x => new EmployeeTableModel
{
eid = x.First().emp_id,
heds = x.GroupBy(h => h.HED).Select(h => new HEDModel
{
hed = h.First().HED,
hours = h.Sum(c => c.HOURS),
amt = h.Sum(c => c.AMOUNTRATE)
}).ToList()
}).ToList()
}).ToList();
public class JobTableModel
{
public string job_no { get; set; }
public List<EmployeeTableModel> emps { get; set; }
}
public class EmployeeTableModel
{
public string emp_id { get; set; }
public List<HEDModel> heds { get; set; }
}
public class THEDModel
{
public string hed { get; set; }
public decimal hours { get; set; }
public decimal amt { get; set; }
}
I have another List<XXTJobTableModel> yjobs already loaded from another data source. What I would like to do, if the job_no is not found in yjobs, then add the job (with employee and he data) to yjobs. If the job_no is found in yjobs, and the emp_id is not found in the Employee table for that job, then add the employee (and hed data) to that jobs. If the employee is found in that job, then just add the HED data to the employees list.
I do not have a preference if I somehow merge xjobs and yjobs or if I load the xjobs directly into the yjobs list.
thanks
Are you looking for a very concise set of LINQ statements to do this merge operation or just any solution at all? What I would do is loop through one set and use LINQ to compare each item to the other set, and add items resulting from the comparison to a third set. Problems like this you have to decompose into manageable pieces, and then recompose it into a larger solution once the smaller pieces are working. Make an attempt and if you get stuck someone on here will surely be able to fill in the blanks.
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.
Need a help with RavenDB.
In my web page I want to have such list:
item1 category1
item2 category2
...
and another one:
category1, number of items
category2, number of items
...
My data structures:
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public string CategoryId { get; set; }
}
public class Category
{
public string Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
}
Index for the first list:
public class Item_WithCategory : AbstractIndexCreationTask<Item>
{
public class Result
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
public Item_WithCategory()
{
Map = items => from item in items
select new
{
Name = item.Name,
CategoryName = LoadDocument<Category>(item.CategoryId).Name
};
}
}
Is this data structure suitable for my case, or will it be better to have Category instead of CategoryId in item structure?
Should I use my index or is there a better solution to take category name?
If my index is good, how to write a correct query? My current try:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>().ToArray();
}
but it throws exception stating that return type is item, not result. How to fix it?
You have a couple of options here. You could store both the CategoryId and the CategoryName on the Item entity. This will of course lead to duplicated data (if you still need to store the Category entity), but "storage is cheap" is a popular term these days.The downside of this is that you need to update each Item document of a given category if the category name changes to keep things consistent. A benefit is that you need to do less work to get your desired result.
If you store Category Name on the item as well you don't need a special index to handle the first list, just query on the Items and return what you need. For the second list you need to create a Map/Reduce index on the Item entity that groups on the category.
However, if you need to use the data structure you've given, there are a couple of ways of solving this. First, it's really not recommended to use a LoadDocument inside of an index definition, especially not in a select statement. This might affect indexing performance in a negative way.
Instead, just index the properties you need to query on (or use an auto index) and then use a Result Transformer to fetch information from related documents:
public class ItemCategoryTransformer : AbstractTransformerCreationTask<Item>
{
public ItemCategoryTransformer()
{
TransformResults = results => from item in results
let category = LoadDocument<Category>(item.CategoryId)
select new ItemCategoryViewModel
{
Name = item.Name,
CategoryName = category.Name
};
}
}
public class ItemCategoryViewModel
{
public string Name { get; set; }
public string CategoryName { get; set; }
}
You can use this Transformer with a Query on the Item entity:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Item>()
.TransformWith<ItemCategoryTransformer, ItemCategoryViewModel>()
.ToList();
}
As for the second list, still using your data structure, you have to use a couple of things. First, a Map/Reduce index over the Items, grouped by CategoryId:
public class Category_Items_Count : AbstractIndexCreationTask<Item, Category_Items_Count.Result>
{
public class Result
{
public string CategoryId { get; set; }
public int Count { get; set; }
}
public Category_Items_Count()
{
Map = items => from item in items
select new Result
{
CategoryId = item.CategoryId,
Count = 1
};
Reduce = results => from result in results
group result by result.CategoryId
into c
select new Result
{
CategoryId = c.Key,
Count = c.Sum(x => x.Count)
};
}
}
But as you only have the CategoryId on the Item entity, you have to use a similar transformer as in the first list:
public class CategoryItemsCountTransformer : AbstractTransformerCreationTask<Category_Items_Count.Result>
{
public CategoryItemsCountTransformer()
{
TransformResults = results => from result in results
let category = LoadDocument<Category>(result.CategoryId)
select new CategoryItemsCountViewModel
{
CategoryName = category.Name,
NumberOfItems = result.Count
};
}
}
public class CategoryItemsCountViewModel
{
public string CategoryName { get; set; }
public int NumberOfItems { get; set; }
}
And lastly, query for it like this:
using (var session = documentStore.OpenSession())
{
var items = session.Query<Category_Items_Count.Result, Category_Items_Count>()
.TransformWith<CategoryItemsCountTransformer, CategoryItemsCountViewModel>()
.ToList();
}
As you can see, there are quite a difference in work needed depending on what data structure you're using. If you stored the Category Name on the Item entity directly you wouldn't need any Result Transformers to achieve the results you're after (you would only need a Map/Reduce index).
However, Result Transformers are executed server side and are only executed on request, instead of using LoadDocument inside of an index which is executed every time indexing occurs. Also, and maybe why LoadDocuments inside of index definitions isn't recommended, every change to a document that's referenced with a LoadDocument in an index will cause that index to have to be rewritten. This might lead to a lot of work for the index engine.
Lastly, to answer your last question about why you get an exception when querying: As the actual return type of your index is the document that that's being indexed (in this case Item). To use something else you need to project your result to something else. This can be done by using ".As()" in the query:
Item_WithCategory.Result[] all;
using (var session = DocumentStoreHolder.Store.OpenSession())
{
all = session.Query<Item_WithCategory.Result, Item_WithCategory>()
.As<Item_WithCategory.Result>()
.ToArray();
}
Long post, but hope it helps!