Correct way to use advanced properties in MVC EF - c#

Let's say I have a model football Team. It plays Matches in a group with other teams. Now I want to select top 2 teams from the list. The score is counted as usual: 3 for the win, 1 for the draw, 0 for the loss.
The model for Match looks like this:
[Key]
public int MatchId{get;set;}
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
[ForeignKey("HomeTeamId")]
public virtual Team HomeTeam { get; set; }
[ForeignKey("AwayTeamId")]
public virtual Team AwayTeam { get; set; }
public int? HomeTeamScored { get; set; }
public int? AwayTeamScored { get; set; }
I've tested this 5 solutions:
1) have a view instead of the table to take the score column from, but it complicates the programming part since I will have to somehow tell EF to use the table for insertion but the view for showing the data
2) Have the column Score as not mapped, then take all teams, count the score like this:
var list = db.Teams.ToList();
foreach(var team in list)
{
team.Score = db.Matches.Where(...).Sum();
}
then just order the list by Score and take first 2.
3) Another way is to have
var list = db.Teams.OrderByDesc(t => db.Matches.Where(...).Sum()).Take(2).ToList();
I will have to do a lot of checking for null, also checking which team won or drawn, whether the team I was looking for played as home or away, etc.
4)
Yet another option would be to recount the Score for the team every time I add/edit a match, however I feel like it is a very unprofessional approach.
As I said, each of these methods are solutions that would lead me to solving the task, but... I am having a sixth sense that I am missing something completely obvious as of how I could make this with the least effort. Can anyone suggest what I am missing?
P.S. If it affects the answer, let's assume I am using the latest version of everything.

When data redundancy looms, often normalization is the solution. I think in your case you need a bit of both, normalization and a bit of redundancy.
The repeated properties in Match are "smelly". They seem to call for normalization. A closer look reveals that this is not true for all of them. A match consists of two teams, always. So the two TeamIds are OK (and the accompanying references). But you could store the scores differently.
Take a look at this possible model:
class Team
{
public int TeamId { get; set; }
// ...
public ICollection<MatchTeam> MatchTeams { get; set; }
}
class Match
{
public int MatchId { get; set; }
public int HomeTeamId { get; set; }
public int AwayTeamId { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team AwayTeam { get; set; }
}
class MatchTeam
{
public int MatchId { get; set; }
public int TeamId { get; set; }
public int Scored { get; set; } // Number of goals/points whatever
public int RankingScore { get; set; } // 0, 1, or 3
}
MatchTeam is an entity that stores the achievements of 1 team in 1 match. The Scored property is normalized result of HomeTeamScored and AwayTeamScored. The advantage is: the property isn't nullable: a MatchTeam entry is created when the results are a fact.
The redundancy is in the RankingScore property. This has to be determined when a match is entered or modified and it depends on (and should be consistent with) the scores. As always with redundancy, there's the danger of data inconsistency. But is it a big danger? If there is only one service method by which MatchTeam data are entered or modified the danger is confined sufficiently.
The advantage is that now it's doable to collect the total scores for each team at runtime:
var topTeams = context.Teams
.OrderByDescending(t => t.MatchTeams.Sum(mt => mt.RankingScore))
.Take(2);

1) I don't understand why implementing a view complicates your programming at at all. That's a good solution. Inserting a match result and getting the top teams are two completely independent operations. Please, let me see some code to understand why you have that strong coupling between them suchs independent things as a match score and a total score of a team.
2) This is a bad option: you need to make a query for all teams, and an additional query for each team. Bad performance!
3) It would be good to see your code to show how you can improve your query. For example making null checking is not that bad. It's as simple as using the ?? operator, i.e. mt => mt.HomeTeamSocred ?? 0 will convert null to 0 quite easily. If you showed the used expression it would be possible to see if it can be improved and simplified. However,I can propose this one, which is not that complicated:
ctx.Match.Select(m => new
{ // Score for HomeTeam
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 3 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 0 : 1,
TeamId = m.HomeTeamId,
})
.Concat(
ctx.Match.Select(m => new
{ // Score for away Team
TeamScore = (m.HomeTeamScored ?? 0) > (m.AwayTeamScored ?? 0)
? 0 : (m.HomeTeamScored ?? 0) < (m.AwayTeamScored ?? 0)
? 3 : 1,
TeamId = m.AwayTeamId,
})
).GroupBy(mr => mr.TeamId) // Group match scores by TeamId's
.Select(mrs=> new
{
TeamId = mrs.Key,
TotalScore = mrs.Sum(m => m.TeamScore)
})
.OrderByDescending(ts => ts.TotalScore)
.Take(2);
However there is something I don't understand. Why a Home/AwayTeamScored can be null? Since before the match starts, the Score must be zero for both teams. That null makes no senses. This would avoid the trobule with checking nulls.
4) What does this option mean?

Related

Entity.HasRequired returning items with NULL property

I have two related entities built and linked with Fluent API.
public class EDeal : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public virtual ECustomer Customer { get; set; }
...etc
}
public class ECustomer : IEntityBase
{
public int ID { get; set; }
public string Customer_id { get; set; }
public string Customer_name { get; set; }
public virtual ICollection<EDeal> Deals { get; set; }
...etc
}
linked with
modelBuilder.Entity<ECustomer>().HasKey(c => c.Customer_id);
modelBuilder.Entity<EDeal>().HasRequired<ECustomer>(s => s.Customer)
.WithMany(r => r.Deals)
.HasForeignKey(s => s.Customer_id);
I recognize that this is inefficient linking but I had to link it in this way because I don't have control over the db structure.
The important thing to note is that the EDeal requires an ECustomer (.HasRequired). The database contains many rows in EDeal that have a null Customer_id field and I do not want to ever pull those lines when I query the entity.
I thought that the .HasRequired would make sure that I never got back any EDeals that do not have ECustomers associated with them but that doesn't seem to be the case. Instead, it only seems to ignore those lines with NULL Customer_id values when I try to order by a property in the Customer. And even then, returning the .Count() of the query behaves strangely.
var count1 = db.Set<EDeal>().Count(); //returns 1112
var count2 = db.Set<EDeal>().ToList().Count(); //returns 1112
var count3 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).Count(); //returns 1112
var count4 = db.Set<EDeal>().OrderBy(c => c.Customer.Customer_name).ToList().Count(); //returns 967
I know I can add a .Where(c => c.Customer.Customer_id != Null) to make sure I only get back what I'm looking for, but I'm hoping for a solution in the Entity's configuration because I have many generic functions acting on my IEntityBase class that build dynamic queries on generic Entities and I don't want to use a workaround for this case.
Questions:
1) Is there a way to limit the entity to only return those EDeals that have a corresponding ECustomer?
2) In my example above, why do count3 and count4 differ?
Thanks in advance.

Querying multiple parameters using Linq with Marten in Visual Studio

I am learning Document Databases, and we are using Marten/Linq in Visual Studio. The database is running through Postgres/PGAdmin. My database is football(not American) leagues, teams, players and managers. I am trying to construct queries based on multiple parameters. I have singular parameters down pretty well.
List<Player> englishPlayers = finalDb.Query<Player>().Where(x => x.Nationality.Contains("English")).ToList();
This query would create a list of all players whose Nationality is set to "English" in the Document Database.
Player is my class/table with a Nationality "field". What I am trying to do is query based on multiple parameters. For instance, I have multiple "fields" that are either an int or bool. As an example, if I wanted to create a query to show all players of a certain Nationality with lifetimeGoals > 100, how would I accomplish this?
I have searched Google for about an hour, and read through the suggested similar questions, but most of those questions don't account for Marten being used.
I've tried breaking it down by individual queries first and then combine them, for instance:
Player m4 = finalDb.Query<Player>().SelectMany(e => e.lifetimeGoals
.Where(p => e.lifetimeGoals >= 0));
However, this throws an error stating
int does not contain a definition for where, and no extension method
'Where' accepting a first argument of type 'int'.
My terminology with this is a little off, but hopefully this is clear enough to find guidance.
Class for Player:
class Player
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Team { get; set; }
public string prefPosition { get; set; }
public string Nationality { get; set; }
public int yearsAtCurrentClub { get; set; }
public int lifetimeGoals { get; set; }
public int domesticTitles { get; set; }
public int europeanTitles { get; set; }
}//Class Player
Main class
static void Main(string[] args)
{
string connectionString = ConfigurationManager.ConnectionStrings
["FinalProjectDB"].ConnectionString;
IDocumentStore store = DocumentStore.For(connectionString);
using (var finalDb = store.OpenSession())
{
Player m4 = finalDb.Query<Player>().SelectMany(p => p.lifetimeGoals)
.Where(p => p.lifetimeGoals >= 0 && p.myString.Equals("valueToCheck"));
Console.ReadLine();
}
You can't use .Where() on an integer. Instead use it like this:
Player m4 = finalDb.Query<Player>().SelectMany(e => e.lifetimeGoals)
.Where(p => p.lifetimeGoals >= 0);
the above query has a close bracket at the end of SelectMany allowing the Where clause to work with the intended query.
Due to adding a bracket on the end of the SelectMany there is then no needed to have an additional bracket at the end of the query.
Edit: You can simply add another clause to your .Where()
Player m4 = finalDb.Query<Player>().SelectMany(e => e.lifetimeGoals)
.Where(p => p.lifetimeGoals >= 0 && p.myString.Equals("valueToCheck"));
You can use && for and or you can use || for or.
Second edit: I don't see why you are using .SelectMany(). You should be able to use your query like this:
Player m4 = finalDb.Query<Player>().Where(p => p.lifetimeGoals >= 0 && p.myString.Equals("valueToCheck")).FirstOrDefault();
Or use .ToList() when you want a list of players.
List<Player> players = finalDb.Query<Player>().Where(p => p.lifetimeGoals >= 0 && p.myString.Equals("valueToCheck")).ToList();

How to index related documents in reverse direction in Ravendb

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 need help speeding up this EF LINQ query

I am using EntityFramework 6 and running into some major speed issues -- this query is taking over two seconds to run. I have spent the better part of the day using LinqPad in order to speed up the query but I could only get it down from 4 to two seconds. I have tried grouping, joins, etc. but the generated SQL looks overly complicated to me. I am guessing that I am just taking the wrong approach to writing the LINQ.
Here is what I am attempting to do
Find all A where Valid is null and AccountId isn't the current user
Make sure the Collection of B does not contain any B where AccountId is the current user
Order the resulting A by the number of B in its collection in descending order
Any A that doesn't have any B should be at the end of the returned results.
I have to models which look like this:
public class A
{
public int Id { get; set; }
public bool? Valid { get; set; }
public string AccountId { get; set; }
public virtual ICollection<B> Collection { get; set; }
}
public class B
{
public int Id { get; set; }
public bool Valid { get; set; }
public string AccountId { get; set; }
public DateTime CreatedDate { get; set; }
public virtual A Property { get; set; }
}
The table for A has about one million rows and B will eventually have around ten million. Right now B is sitting at 50,000.
Here is what the query currently looks like. It gives me the expected results but I have to run an orderby multiple times and do other unnecessary steps:
var filterA = this.context.A.Where(gt => gt.Valid == null && !gt.AccountId.Contains(account.Id));
var joinedQuery = from b in this.context.B.Where(gv => !gv.AccountId.Contains(account.Id))
join a in filterA on gv.A equals a
where !a.Collection.Any(v => v.AccountId.Contains(account.Id))
let count = gt.Collection.Count()
orderby count descending
select new { A = gt, Count = count };
IQueryable<GifTag> output = joinedQuery
.Where(t => t.A != null)
.Select(t => t.A)
.Distinct()
.Take(20)
.OrderBy(t => t.Collection.Count);
Thanks
Well you could always try to remove these two lines from the joinQuery
where !a.Collection.Any(v => v.AccountId.Contains(account.Id))
and
orderby count descending
the first line have already been filtered in the first Query
and the orderline, well do do the ordering on the last Query so there is no point in doing it twice

Using Linq to order multiple lists from multiple tables

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.

Categories