Merging data from two tables into one view - c#

I am having some trouble getting the data from two different tables into one view. If you know how to do this please let me know. This is what I'm working with:
I have four tables:
public class CoinAllocation
{
public int CoinAllocationID { get; set; }
public int? StoreID { get; set; }
public int? TimeFrameID { get; set; }
public virtual Store Store { get; set; }
public virtual TimeFrame TimeFrame { get; set; }
public virtual List<CoinAllocationItem> CoinAllocationItems { get; set; }
}
public class CoinAllocationItem
{
public int CoinAllocationItemID { get; set; }
public int? CoinID { get; set; }
public int? StoreID { get; set; }
public int? CoinAllocationID { get; set; }
public int QuantityAllocated { get; set; }
public virtual Coin Coin { get; set; }
}
public class CoinUsed
{
public int CoinUsedID { get; set; }
public int? TimeFrameID { get; set; }
public int? StoreID { get; set; }
public virtual Store Store { get; set; }
public virtual TimeFrame TimeFrame { get; set; }
public virtual List<CoinUsedItem> CoinUsedItems { get; set; }
}
public class CoinUsedItem
{
public int CoinUsedItemID { get; set; }
public int? CoinUsedID { get; set; }
public int? CoinID { get; set; }
public int? QuantityUsed { get; set; }
public virtual Coin Coin { get; set; }
public int? StoreID { get; set; }
}
Now, I need iterate through these tables to find coins that are from the same store and the same time frame. Then, I need to combine coins with the same ID, total their allocation amount, and then total the amount that they have used. Last, I need to get them into one view that is set up like this:
Coin Name | Amount Allocated | Amount Used | Remaining
silver coin 10 1 9
gold coin 15 5 10
and so on...
So, if there are two silver coins from the same store during the same time frame, they show up in the table in just one line, with the totals.
The problem I am having is getting the allocated from one table and getting the used from the other table.
Anyone out there who can help will be amazing.

Generally, you have to consider the following steps:
Filter your result by desired TimeFrame and Shop (LINQ Where)
Select the properties that you are interested in or that are needed for further computations (LINQ Select and SelectMany)
Group the results and compute sums (LINQ GroupBy)
Join different sub-results, select final properties (LINQ Join and GroupJoin)
There's always more than one way. I imagine that using GroupJoin at some point might be more efficient than what I currently came up with and if you start with Coin instead of separately handling CoinAllocation and CoinUsed, you might get a better structured code depending on the available navigation properties...
The following is what I came up with, which might or might not satisfy your needs - there are some uncertainties in your presented model and criteria.
// whatever you search for... this assumes you want coins for one store in one timeframe
int desiredStoreID = 0, desiredTimeFrameID = 0;
var coinUsedSelection = db.CoinUsed
.Where(x => x.StoreID == desiredStoreID && x.TimeFrameID == desiredTimeFrameID)
.SelectMany(x => x.CoinUsedItems)
.GroupBy(x => x.CoinID, x => x.QuantityUsed, (k, v) => new { CoinID = k, QuantityUsedSum = v.Sum() });
var coinAllocationSelection = db.CoinAllocations
.Where(x => x.StoreID == desiredStoreID && x.TimeFrameID == desiredTimeFrameID)
.SelectMany(x => x.CoinAllocationItems)
.GroupBy(x => new { x.CoinID, x.Coin.CoinName }, x => x.QuantityAllocated, (k, v) => new { k.CoinID, k.CoinName, QuantityAllocatedSum = v.Sum() });
var result = coinAllocationSelection.Join(coinUsedSelection, ca => ca.CoinID, cu => cu.CoinID, (ca, cu) => new
{
CoinName = ca.CoinName,
AmountAllocated = ca.QuantityAllocatedSum,
AmountUsed = cu.QuantityUsedSum,
Remaining = ca.QuantityAllocatedSum - cu.QuantityUsedSum
})
.ToList();

Related

One to zero or one relationship Entity Framework, getting the relation of an entity

I'm trying to establish a one to zero or one relationship in EF Core, but I can't seem to go into a model and get it's related model.
public class Voucher
{
[Key]
public long id { get; set; }
public long voucherId { get; set; }
public long number { get; set; }
public string Type { get; set; }
public string description { get; set; }
public DateTime date { get; set; }
public int paymentId { get; set; }
public Invoice invoice { get; set; }
[ForeignKey("client")]
public long clientFK { get; set; }
public Client client { get; set; }
public ICollection<Post> posts { get; set; } = new List<Post>();
}
public class Invoice
{
[Key, ForeignKey("voucher")]
public long voucherFK { get; set; }
public long invoiceId { get; set; }
public string clientId { get; set; }
public DateTime dueDate { get; set; }
public decimal amountTotal { get; set; }
public string specification { get; set; }
public string invoicePdf { get; set; }
public long orderId { get; set; }
public Voucher voucher { get; set; }
public ICollection<InvoiceLine> invoiceLines { get; set; } = new List<InvoiceLine>();
}
Now I can create Vouchers without the need for an invoice, and unnable to create an invoice without a voucher. (I've also tried several other ways to map this relationship)
The problem is when I'm trying to fetch invoices that are bound to vouchers or the other way around.
Here's an example of how I tried to do it:
[HttpGet("testing1")]
public List<Voucher> getInvoiceTest(string filter)
{
long tenantId = getTenantId();
DateTime comparisonDate = compareDates(filter);
var invoices = _warehouseDb.Invoices
.Where(v => v.voucher.client.tenantFK == tenantId)
.Where(d => d.voucher.date >= comparisonDate)
.OrderByDescending(p => p.voucher.paymentId).ThenByDescending(d => d.voucher.date)
.ToList();
//return invoices;
List<Voucher> vouchers = new List<Voucher>();
for (int i = 0; i < invoices.Count - 1; i++)
{
if (invoices[i].voucher != null)
{
Console.WriteLine("i value: " + i);
Voucher voucher = new Voucher();
voucher = invoices[i].voucher;
vouchers.Add(voucher);
}
}
return vouchers;
}
I've tried without the ending forloop to see if I actually get a list of invoices, and I do. The filter part is just to get it within the correct time-span.
Once I reach the forloop, it can't seem to get any vouchers connected to any of the invoices.
Have I connected the models wrong or what's going on? This exact type of code works for other models where the relationship is one-to-many.
I've also tried mapping the models with virtual tag to see if it made any difference. I also had invoices save a FK for vouchers, but my main goal is to go through a list of vouchers and get it's invoice.
Any suggestion is appreciated, if not, thanks for reading.
I'm using Entity framework core and postgreSQL

LINQ Group By Count for Many to Many

I have 2 entities, with a 1 to many relationship, and I'm going to switch it to many to many but I need help with grouping and counts.
SearchString -> many JobResults
A SearchSting is used to find job results and job results are stored as a collection property of SearchString:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual SearchString SearchString { get; set; }
}
I get the top 5 JobFunctions of all job results as follows:
var top5jobfunctions = JobSearchResults.Where(a => (a.SearchString != null)).
GroupBy(s => new { s.SearchString.JobFunction.JobFunctionId, s.SearchString.JobFunction.JobFunctionName }).
Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() }).
OrderByDescending(x => x.count).
Take(5).ToList();
I'm going to switch it to many to many as such:
public class SearchString
{
public int SearchStringId { get; set; }
public string SearchStringName { get; set; }
public string query { get; set; }
public JobFunction JobFunction { get; set; }
public JobSeniority JobSeniority { get; set; }
public virtual ICollection<JobSearchResult> results { get; set; }
}
public class JobSearchResult
{
public int JobSearchResultId { get; set; }
public string jobtitle { get; set; }
public string company { get; set; }
public virtual ICollection<SearchString> SearchStrings { get; set; }
}
How do I get my top 5 jobfunctions counts once I switch it to many to many?
Also, is the structure I chose the right approach? For example I wonder if having jobresults a child collection of SearchString was maybe not the best way to go and that perhaps I should just have SearchStrings be a collection property of JobResult.
For the modified model with many many relationship, consider the following modification to your original query:
var top5jobfunctions =
JobSearchResults.SelectMany(j => j.SearchString.Select(s => new {j,s}))
.Where(j => (j.s != null))
.GroupBy(j => new { j.s.JobFunction.JobFunctionId, j.s.JobFunction.JobFunctionName })
.Select(g => new { value = g.Key.JobFunctionId, displayname = g.Key.JobFunctionName, count = g.Count() })
.OrderByDescending(x => x.count)
.Take(5).ToList();
Explanation:
Now since JobSearchResult contains ICollection<SearchString>, it needs flattening to execute a similar query as earlier
SelectMany flattens the data and fills the results as an anonymous type, which contains a record for each SearchString
Henceforth similar logic as you have designed is followed
Model Correctness
I would not prefer, this kind of relationship, as it makes overall querying and data insertion unnecessarily complex
In my understanding a 1 to Many relationship would do as good a job in fetching all the relevant information, in this case you may consider just having ICollection<JobSearchResult> aggregated inside SearchString or vice versa relationship based on suitability, I am not sure what kind of use case does a circular many many relationship model solve.

Get data from nested table using entity framework

First of all this is my first question in the forum so please excuse me for any writing mistake.
I have 4 tables
attaching the table diagram
What I want is to get list of attraction name joining 'tblattraction' with 'tblattractionmaster' and count of the exact attraction for each place from 'tblattractions' using 'locationid' , I am using entity framework but don't know how to do that,
Disclaimer:
Each location can consist Multiple Places
Each Place can consist Multiple Attractions
What I have tried
return context.tblLocationMasters.Select(t => new details()
{
locationid = t.LocationId,
locationname = t.LocationName,
attractions =t.tblPlaces.SelectMany(a => a.tblAttractions).Select(b => new attractions(){
AttractionName=b.tblAttractionMaster.attractionname//(Not working),
TotalAttractions=0//???
}).ToList()
}).ToList();
I recreated your model (slightly different) using Code First. I came up with the following structure:
public class Location
{
public int LocationId { get; set; }
public string LocationName { get; set; }
public ICollection<Place> Places { get; set; }
}
public class Place
{
public int PlaceId { get; set; }
public string PlaceName { get; set; }
public int LocationId { get; set; }
public Location Location { get; set; }
public ICollection<AttractionPlace> Attractions { get; set; }
}
public class Attraction
{
public int AttractionId { get; set; }
public string AttractionName { get; set; }
}
public class AttractionPlace
{
public int AttractionPlaceId { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
public int AttractionId { get; set; }
public Attraction Attraction { get; set; }
}
Then, I could get the results in the way you needed with the following query:
var query = (from loc in db.Locations
join pla in db.Places.Include(x => x.Attractions) on loc.LocationId equals pla.LocationId
let count = pla.Attractions.Count()
select new
{
loc.LocationId,
loc.LocationName,
Attractions = pla.Attractions.Select(z => new
{
pla.PlaceName,
z.AttractionId,
z.Attraction.AttractionName
}),
AttractionsByPlaceCount = count
});
The query above returns data in this format
Just a side note though: I didn't went further to see the performance of this query. The SQL generated by Linq wasn't that bad, but you should consider analyzing it before actually using it in production.

ravendb index, query on child collection

I want to get a summary of all products, as only the latest OrderHistory is of interest where I want to use this. I have thousands of products with hundreds of OrderHistory each, but now I only want the product id and the latest OrderHistory for each product.
public class ProductSummary
{
public int ProductId { get; set; }
public OrderHistory LastOrderHistory { get; set; }
}
The OrderHistory is stored inside the Product document like this:
public class Product
{
public int Id { get; set; }
public int MarketGroupId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<OrderHistory> OrderHistory { get; set; }
}
And this is what OrderHistory looks like:
public class OrderHistory
{
public long OrderCount { get; set; }
public long Volume { get; set; }
public DateTime date { get; set; }
public double AvgPrice { get; set; }
}
Now I've tried a few approaches on the index and query to get this running, this is my latest try, but it returns no results.
public class LatestProductOrderHistory : AbstractIndexCreationTask<Product, ProductSummary>
{
public LatestProductOrderHistory()
{
Map = products => from p in products
from oh in p.OrderHistory
select new
{
ProductId = p.Id,
LastOrderHIstory = p.OrderHistory.OrderByDescending(o => o.date).Last()
};
StoreAllFields(FieldStorage.Yes);
}
}
And finally my query:
var results = session
.Query<ProductSummary, LatestProductOrderHistory>()
.ProjectFromIndexFieldsInto<ProductSummary>()
.Take(1024)
.Skip(start)
.ToList();
This combination gives me no results, I have never made indexes in ravendb before, so I'm sorry if this is a dumb question.
EDIT: Well, I'm not sure what I changed, but now I'm getting "Could not read value for property: Id"
EDIT2: The strange issue in previous edit was solved by restarting vs and ravendb, so current result is no result
As Ayende commented, you must add the Reduce function to your index as:
Reduce = results => from result in results
group result by result.Id into g
select new
{
Id = g.Key,
LastOrderHistory = g.SelectMany(x=> x.LastOrderHistory)
.OrderByDescending(o => o.Date).FirstOrDefault()
};
Just selecting wanted fields in Map function does not make your index Map/Reduce.
Then query your index as:
session.Query<ProductSummary, LatestProductOrderHistory>()

How can I user LINQ to get a IList<string> from a multi-table join?

I have a SQL Server 2012 database and I am using Entity Framework 6.1 to access data.
I have the following entities that are joined to each other with Primary Key and Foreign Key relationships. What I would like to do is to get a simple collection of values of the Quid parameter in the Questions table where the taskId matches a value that is chosen outside of this code.
Task contains Objective contains ObjectiveDetail contains SubTopic contains Problems contations
Questions
I created the following LINQ statement. It seems not to work and I need some advice on what I am doing wrong and how the statement could be made to get what I need. In particular I am not sure about the way I do the join with all the many .Selects and also the select to give me the output.
var quids = db.Tasks
.Include(e => e.Objectives
.Select(o => o.ObjectiveDetails
.Select(od => od.SubTopics
.Select(s => s.Problems
.Select(p => p.Questions)))))
.Where(t => t.TaskId == taskId)
.Select(e => e.Objectives
.Select(o => o.ObjectiveDetails
.Select(od => od.SubTopics
.Select(s => s.Problems
.Select(p => p.Questions
.Select(q => q.QuestionUId))))))
.ToList();
However this gives me a very confusing output and certainly not the simple IList of Quids
that I would like. Here are my classes for reference. I removed additional fields and hopefully just left the important ones.
public class Task
{
public Task()
{
this.Objectives = new HashSet<Objective>();
}
public int TaskId { get; set; }
public virtual ICollection<Objective> Objectives { get; set; }
}
public class Objective
{
public Objective()
{
this.ObjectiveDetails = new HashSet<ObjectiveDetail>();
}
public int ObjectiveId { get; set; }
public int TaskId { get; set; }
public virtual Task Task { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
}
public partial class ObjectiveDetail
{
public ObjectiveDetail()
{
this.SubTopics = new HashSet<SubTopic>();
}
public int ObjectiveDetailId { get; set; }
public int ObjectiveId { get; set; }
public virtual Objective Objective { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public class SubTopic
{
public SubTopic()
{
this.Problems = new HashSet<Problem>();
}
public int SubTopicId { get; set; }
public int Number { get; set; }
public int TopicId { get; set; }
public virtual ICollection<ObjectiveDetail> ObjectiveDetails { get; set; }
public virtual ICollection<Problem> Problems { get; set; }
}
public class Problem
{
public Problem()
{
this.Questions = new HashSet<Question>();
}
public int ProblemId { get; set; }
public int SubTopicId { get; set; }
public virtual SubTopic SubTopic { get; set; }
public virtual ICollection<Question> Questions { get; set; }
}
public class Question
{
public int QuestionId { get; set; }
public string QuestionUId { get; set; }
public virtual Problem Problem { get; set; }
}
Please note the many-many relationship between SubTopic and ObjectiveDetail. This I think makes it more difficult.
From what I understand of your need, you can simply start from the Question DbSet instead of the task:
var uids = db.Questions.Where(q => q.Problem.SubTopic
.ObjectiveDetails
.Any(od => od.Objective.TaskId == taskId))
.Select(q => q.QuestionUId)
.ToList();
This will take all Question which has at least one objective with the TaskId equal to taskId then project the QuestionUid and put it in a list.
Each of your outer Select calls produces a sequence for each item. This gives you nested sequences and probably a confusing ToString debug output.
What you probably meant was to use SelectMany to flatten the nested sequences.
The Include in your query cannot possibly help because you are just returning a list of strings. There is nothing to include in a string. Better remove it. Who knows what code EF generates from it.

Categories