lookup tables, enums and how to query them - asp.net

I have a table
Images
-Id
-Name
-StatusId
and a lookup table:
ImagesStatus
-Id
-Name
what would be the best practice to do a query on the images table based on StatusId?
1) Hardcode the Id:
db.Images.Where(x => x.StatusId == 1);
3) Create an enum (ImagesStatusEnum) that matches the ImagesStatus table elements and then do:
public enum ImagesStatusEnum
{
Pending = 1,
Approved = 2,
Rejected = 3
}
int approvedStatusId = (int)ImagesStatusEnum.Approved;
db.Images.Where(x => x.StatusId == approvedStatusId).ToList();
3) Something else I haven't thought about?

In EF5 and newer you can map a field on an entity to an existing enum. Right click the field in the model (type preferably int) and click convert to enum. You can then setup to use a new enum, or an existing one.
You can do your query like this:
db.Images.Where(x => x.StatusId == ImagesStatusEnum.Approved).ToList();
I prefer the enum way since it gives a bit more readability. The cost of maintaining these enums are minimal.

Related

Using ToLookup too slow (C#, .NET Core, EntityFrameworkCore)

I need help to speed up this chunk of code somehow.
Using EntityFrameworkCore 3, .NET Core 3.1.
var groups = _context.IPItem
.OrderBy(x => x.ParentId).ThenBy(x => x.OrderInIndex)
.Where(x => x.BibNum == BibNumber)
.ToLookup(x => x.ParentId, x => new IPItemViewModel
{
IPItemId = x.IPItemId,
ResourceText = x.ResourceText,
ResourceLink = x.ResourceLink,
FullResourceLink = x.FullResourceLink,
OrderInIndex = x.OrderInIndex,
ExtraInfo = x.ExtraInfo
});
The "ToLookup" can be a big bottleneck when there are several layers of nested ParentIds associated with the particular BibNumber.
I'd love to see what the "ToLookup" part looks like in TSQL but can't seem to find a way to do that.
I was trying to figure out if changing ToLookup to GroupBy, might be faster, but I'm not sure how to do that.
Any suggestions?
The table for this has these properties:
IPItemId int NOT NULL, Primary Key
ParentId int NULL
BibNum string NOT NULL
ResourceText string NOT NULL
ResourceLink string NOT NULL
FullResourceLink string NOT NULL
OrderInIndex int NULL
ExtraInfo string NOT NULL
I was able to speed up the query by implementing an Index on the table in the database.
CREATE NONCLUSTERED INDEX IX_IndexPageItem_bibNum_parent
ON IPItem (bibNum ASC, ParentId ASC, orderInIndex ASC);
It only sped up the process when I implemented the Index before inserting everything to the database, and not after.
Thanks all for taking a look, appreciated!

Unique serial number generation - Entity Framework ASp.net MVC

I am developing a complaint management in which I have to generate unique serial number for each complaint like 00001/20 {Serial number/year}.
I am using repository pattern and i am generating this complaint number using the following code snippet but problem is if two user try to lodge a complaint at the same time it will generate a same complaint no and that thrown an error as I am keeping a serial number in a separate table which is also mentioned below for reference. Let me know the best way to achieve this
int serialNo = repository.serialNo.Find(c => c.Year == DateTime.Now.Year).FirstOrDefault().TicketCounter;
string complaintNo = string.Format("{0}", serialNo.ToString().PadLeft(5, '0'));
model.Id = repository.complaintRepo.GetMaxPK(c => c.Id);
I am using repository pattern.
I guess, one of the solutions is to setup the table so that it generates required ID automatically on every new row. This ensures that the ID is always unique.
CREATE SEQUENCE MySequence
AS int
START WITH 1
INCREMENT BY 1;
CREATE TABLE Complaint
(
Id char(8) CONSTRAINT [DF_Complaint_ID]
DEFAULT FORMAT((NEXT VALUE FOR MySequence), '0000#')
+'/'+RIGHT(YEAR(GETDATE()),2),
Foo int,
Bar int,
CONSTRAINT [PK_MyTable] PRIMARY KEY (Id)
);
Demo: https://dbfiddle.uk/?rdbms=sqlserver_2017&fiddle=18a5d0fec80a3985e30cef687d3c8e49
So there will be no need to assign the id manually and your code could look like
var c = repository.Insert(new model
{
Foo = ...
Bar = ...,
...
});
repository.Save();
// you can get id after inserting data in the database
string id = c.Id;

Efficient joining the most recent record from another table in Entity Framework Core

I am comming to ASP .NET Core from PHP w/ MySQL.
The problem:
For the illustration, suppose the following two tables:
T: {ID, Description, FK} and States: {ID, ID_T, Time, State}. There is 1:n relationship between them (ID_T references T.ID).
I need all the records from T with some specific value of FK (lets say 1) along with the related newest record in States (if any).
In terms of SQL it can be written as:
SELECT T.ID, T.Description, COALESCE(s.State, 0) AS 'State' FROM T
LEFT JOIN (
SELECT ID_T, MAX(Time) AS 'Time'
FROM States
GROUP BY ID_T
) AS sub ON T.ID = sub.ID_T
LEFT JOIN States AS s ON T.ID = s.ID_T AND sub.Time = s.Time
WHERE FK = 1
I am struggling to write an efficient equivalent query in LINQ (or the fluent API). The best working solution I've got so far is:
from t in _context.T
where t.FK == 1
join s in _context.States on t.ID equals o.ID_T into _s
from s in _s.DefaultIfEmpty()
let x = new
{
id = t.ID,
time = s == null ? null : (DateTime?)s.Time,
state = s == null ? false : s.State
}
group x by x.id into x
select x.OrderByDescending(g => g.time).First();
When I check the resulting SQL query in the output window when executed it is just like:
SELECT [t].[ID], [t].[Description], [t].[FK], [s].[ID], [s].[ID_T], [s].[Time], [s].[State]
FROM [T] AS [t]
LEFT JOIN [States] AS [s] ON [T].[ID] = [s].[ID_T]
WHERE [t].[FK] = 1
ORDER BY [t].[ID]
Not only it selects more columns than I need (in the real scheme there are more of them). There is no grouping in the query so I suppose it selects everything from the DB (and States is going to be huge) and the grouping/filtering is happening outside the DB.
The questions:
What would you do?
Is there an efficient query in LINQ / Fluent API?
If not, what workarounds can be used?
Raw SQL ruins the concept of abstracting from a specific DB technology and its use is very clunky in current Entity Framework Core (but maybe its the best solution).
To me, this looks like a good example for using a database view - again, not really supported by Entity Framework Core (but maybe its the best solution).
What happens if you try to do a more straight forward translation to LINQ?
var latestState = from s in _context.States
group s by s.ID_T into sg
select new { ID_T = sg.Key, Time = sg.Time.Max() };
var ans = from t in _context.T
where t.FK == 1
join sub in latestState on t.ID equals sub.ID_T into subj
from sub in subj.DefaultIfEmpty()
join s in _context.States on new { t.ID, sub.Time } equals new { s.ID, s.Time } into sj
from s in sj.DefaultIfEmpty()
select new { t.ID, t.Description, State = (s == null ? 0 : s.State) };
Apparently the ?? operator will translate to COALESCE and may handle an empty table properly, so you could replace the select with:
select new { t.ID, t.Description, State = s.State ?? 0 };
OK. Reading this article (almost a year old now), Smit's comment to the original question and other sources, it seems that EF Core is not really production ready yet. It is not able to translate grouping to SQL and therefore it is performed on the client side, which may be (and in my case would be) a serious problem. It corresponds to the observed behavior (the generated SQL query does no grouping and selects everything in all groups). Trying the LINQ queries out in Linqpad it always translates to a single SQL query.
I have downgraded to EF6 following this article. It required some changes in my model's code and some queries. After changing .First() to .FirstOrDefault() in my original LINQ query it works fine and translates to a single SQL query selecting only the needed columns. The generated query is much more complex than it is needed, though.
Using a query from NetMage's answer (after small fixes), it results in a SQL query almost identical to my own original SQL query (there's only a more complex construct than COALESCE).
var latestState = from s in _context.States
group s by s.ID_T into sg
select new { ID = sg.Key, Time = sg.Time.Max() };
var ans = from t in _context.T
where t.FK == 1
join sub in latestState on t.ID equals sub.ID into subj
from sub in subj.DefaultIfEmpty()
join s in _context.States
on new { ID_T = t.ID, sub.Time } equals new { s.ID_T, s.Time }
into sj
from s in sj.DefaultIfEmpty()
select new { t.ID, t.Description, State = (s == null ? false : s.State) };
In LINQ it's not as elegant as my original SQL query but semantically it's the same and it does more or less the same thing on the DB side.
In EF6 it is also much more convenient to use arbitrary raw SQL queries and AFAIK also the database views.
The biggest downside of this approach is that full .NET framework has to be targeted, EF6 is not compatible with .NET Core.

LINQ - listview - 2 levels of data in one table

I have been trying to solve this problem and I can't seem to figure it out. I'm not sure if it's because of my db design and LINQ, but I'm hoping for some direction here.
My db table:
Id         Name         ParentId
1          Data1        null
2          Data2        null
3          Data3        null
4          Data4        1
5          Data5        1
6          Data6        2
7          Data7        2
Basically Data1 and Data2 are the top levels that I want to use for headings and their children will be related based on their ParentID.
I am trying to use a listview to present the data like the following:
Data1
-----
Data4
Data5
Data2
-----
Data6
Data7
I am trying to use a combination of LINQ and listview to accomplish this.
The following is the code for the linq query:
var query = from data in mydb.datatable
where data.ParentId == null
select data;
But this only gives the heading level... and unfortunately listview only takes in 1 datasource.
While it's possible with some databases (like SQL Server post 2005) to write recursive queries, I don't believe those get generated by LINQ. On the other hand, if the number of records is sufficiently small, you could materialize the data (to a list) and write a LINQ query that uses a recursive function to generate your list.
This is from memory, but it would look something like this:
Func<int?,IEnumerable<data>> f = null;
f = parentId => {
IEnumerable<data> result = from data in mydb.datatable
where data.ParentId = parentId
select data;
return result.ToList().SelectMany(d=>f(d.Id));
};
That should get you the hierarchy.
If your hierarchy has only two levels you can use a group join and anonymous objects:
var query = from data in mydb.datatable.Where(x => x.ParentId == null)
join child in mydb.datatable.Where(x => x.ParentId != null)
on data.Id equals child.ParentId into children
select new { data, children };
Edit: You will have to convert the data to a collection that can be bound to a ListView. One hack would be to have a list that is only one level deep with spacing in front of the subitems:
var listViewItems = (from item in query.AsEnumerable()
let dataName = item.data.Name
let childNames = item.children.Select(c => " " + c.Name)
from name in dataName.Concat(childNames)
select new ListViewItem(name)).ToArray();
You could also try to find a control that fits better, like a TreeView. You might want to ask a separate question about this issue.
I just wrote up a blog post describing a solution to build a graph from a self-referencing table with a single LINQ query to the database which might be of use. See http://www.thinqlinq.com/Post.aspx/Title/Hierarchical-Trees-from-Flat-Tables-using-LINQ.

Entity Framework - joining nulls - NullReferenceException was unhandled by user code

I am writing an asp.net mvc application to learn Entity Framework with and am running into an issue that I do not know how to handle. For simplicity lets take the following table structure:
Movie
ID (int, not null, auto increment)
Name (varchar)
GenreID (int)
and
Genre
ID (int, not null, auto increment)
Name (varchar)
Movie.GenreID is a FK reference to Genre.ID
I have brought across all of the tables using the visual designer in VS 2008 and tried the following Linq query:
IEnumerable<Movie> movieList = from f in dataContext.MovieSet.Include("Genre").ToList();
I can output the data in a view using:
<%= Html.Encode( movieList.Genre.Name ) %>
Everything works just fine until I have an item in the Movie table with a null GenreID. Is there something I can do to this query to make it still be able to output (just leave it blank when applicable) or am I doing something horribly wrong?
The problem is that movieList.Genre is null, and you can't access the Name property of a null object.
You can solve this by writing <%= Html.Encode(movieList.Genre == null ? String.Empty : movieList.Genre.Name) %>.
If you don't want your views to be so verbose, you could add a GenreName property to the Movie entity class and move the null check there.
This is probably much more readable than .Include("")
from f in dataContext.MovieSet
select new
{
Name = f.Name,
Genre = f.Genre // This effectively performs a join.
...
}
You can also check for the problem that way:
from f in dataContext.MovieSet
select new
{
Name = f.Name,
GenreName = f.Genre == null ? "" : f.Genre.Name
...
}
This gives you more flexibility, for instance:
from f in dataContext.Genres
select new
{
Name = f.Name
Movies = from movie in f.Movies
where movie.Duration > 240
select movied
}

Resources