LINQ Expression for CROSS APPLY two levels deep - c#

Fairly new to LINQ and am trying to figure out how to write a particular query. I have a database where each CHAIN consists of one or more ORDERS and each ORDER consists of one or more PARTIALS. The database looks like this:
CREATE TABLE Chain
(
ID int NOT NULL PRIMARY KEY CLUSTERED IDENTITY(1,1),
Ticker nvarchar(6) NOT NULL,
Company nvarchar(128) NOT NULL
)
GO
CREATE TABLE [Order]
(
ID int NOT NULL PRIMARY KEY CLUSTERED IDENTITY(1,1),
Chart varbinary(max) NULL,
-- Relationships
Chain int NOT NULL
)
GO
ALTER TABLE dbo.[Order] ADD CONSTRAINT FK_Order_Chain
FOREIGN KEY (Chain) REFERENCES dbo.Chain ON DELETE CASCADE
GO
CREATE TABLE Partial
(
ID int NOT NULL PRIMARY KEY CLUSTERED IDENTITY(1,1),
Date date NOT NULL,
Quantity int NOT NULL,
Price money NOT NULL,
Commission money NOT NULL,
-- Relationships
[Order] int NOT NULL
)
GO
ALTER TABLE dbo.Partial ADD CONSTRAINT FK_Partial_Order
FOREIGN KEY ([Order]) REFERENCES dbo.[Order] ON DELETE CASCADE
I want to retrieve the chains, ordered by the earliest date among all the partials of all the orders for each particular chain. In T-SQL I would write the query as this:
SELECT p.DATE, c.*
FROM CHAIN c
CROSS APPLY
(
SELECT DATE = MIN(p.Date)
FROM PARTIAL p
JOIN [ORDER] o
ON p.[ORDER] = o.ID
WHERE o.CHAIN = c.ID
) AS p
ORDER BY p.DATE ASC
I have an Entity Framework context that contains a DbSet<Chain>, a DbSet<Order>, and a DbSet<Partial>. How do I finish this statement to get the result I want?:
IEnumerable<Chain> chains = db.Chains
.Include(c => c.Orders.Select(o => o.Partials))
.[WHAT NOW?]
Thank you!

.[WHAT NOW?]
.OrderBy(c => c.Orders.SelectMany(o => o.Partials).Min(p => p.Date))
Here c.Orders does join Chain to Order, while o.SelectMany(o => o.Partials) does join Order to Partial. Once you have access to Partial records, you can use any aggregate function, like Min(p => p.Date) in your case.

Related

Reproduce GroupBy statement using Linq

I'm developing a C# library with .NET Framework 4.0 and Entity Framework 6.1.3.
I want to get the same result that this sql statement:
select c.CODE_LEVEL
from CODES c
where c.CODE in
(Select CODE
from AGGREGATION_CHILDS
where PARENT_CODE = N'1')
group by c.CODE_LEVEL
with entity framework.
I'm using repository pattern, and I have tried this:
List<AGGREGATION_CHILDS> childrenCodeLevel =
m_AggChildRepo
.SearchFor(a => a.PARENT_CODE == aggregation.PARENT_CODE).GroupBy(a => a.Code.CODE_LEVEL).SelectMany(a => a).ToList();
With this code I get a list of AGGREGATION_CHILDS and I only need CODE_LEVEL value.
SearchFor implementation:
public IQueryable<TEntity> SearchFor(Expression<Func<TEntity, bool>> predicate)
{
return _dbSet.Where(predicate);
}
This is the sql tables creation statements:
CREATE TABLE [dbo].[CODES]
(
[CODE] [nvarchar](20) NOT NULL,
[CODE_LEVEL] [tinyint] NOT NULL,
CONSTRAINT [PK_CODES] PRIMARY KEY CLUSTERED
(
[CODE] ASC
)
)
CREATE TABLE [dbo].[AGGREGATION_CHILDS]
(
[CODE] [nvarchar](20) NOT NULL,
[PARENT_CODE] [nvarchar] (20) NOT NULL,
[POSITION] [int] NOT NULL,
CONSTRAINT [PK_AGGREGATION_CHILDS] PRIMARY KEY CLUSTERED
(
[CODE] ASC
),
CONSTRAINT [FK_AGGREGATION_CHILDS_AGGREGATIONS] FOREIGN KEY ([PARENT_CODE]) REFERENCES [AGGREGATIONS]([CODE]) ON DELETE CASCADE,
CONSTRAINT [FK_AGGREGATION_CHILDS_CODES] FOREIGN KEY ([CODE]) REFERENCES [CODES]([CODE])
)
All records in AGGREGATION_CHILDS for the same PARENT_CODE will have the same CODE_LEVEL.
How can I reproduce that sql statement?
Since you are not using any aggregate the group by will just removed the duplicates, same as using Distinct.
var res = db.CODES.Where(x =>
db.AGGREGATION_CHILDS.Any(y => y.PARENT_CODE == 1 && x.Code == y.Code))
.Select(x => x.CODE_LEVEL)
.Distinct();

ASP.NET MVC 5 - LINQ Query to Select Data from Database

I'm working on a school project in ASP.NET MVC 5. The project is about creating a social network. After the user logs in, he will see all public posts on his newsfeed.
I am having issues, though, in showing the public posts' data from the database.
This is the script of the database :
create table Utilizador(
id_utilizador integer not null identity(1,1),
nome varchar(50) not null,
apelido varchar(50) not null,
username varchar(15) not null unique,
pass varchar(50) not null,
email varchar(50) not null unique,
sexo char(1) not null CHECK (sexo IN('M', 'F')),
paĆ­s varchar(50) not null,
imagem_perfil varchar(50) not null,
data_nascimento date not null,
estado int not null default 2, --0->Bloqueado 1-Activo, 2-por activar
primary key (id_utilizador),
check (email LIKE '%#%.%')
)
create table Post(
id_post integer not null identity(1,1),
texto varchar(400) not null,
primary key(id_post)
)
create table Publish_Post(
id_post integer not null,
id_utilizador integer not null,
data timestamp not null,
primary key(id_post),
foreign key(id_post) references Post(id_post),
foreign key(id_utilizador) references Utilizador(id_utilizador)
)
create table Privacy(
id_privacidade integer not null identity(1,1), --> 1 public, 2 private
nome varchar(50) not null,
primary key(id_privacidade)
)
create table Have_Privacy(
id_post integer not null,
id_privacidade integer not null,
primary key(id_post),
foreign key(id_post) references Post(id_post),
foreign key(id_privacidade) references Privacidade(id_privacidade)
)
Let me explain why I create the database the way I do:
The user creates and publishes some posts that have will have a privacy value (1 or 2). After the user logs in, all public posts(1) should appear on his newsfeed.
So far I have this LINQ query in C#:
var id_posts = from p in db.Posts
select p.texto;
ViewBag.Posts = id_posts;
Can someone help me?
Thanks in advance :)
Just do this
var id_posts = from p in db.Posts
join hp in db.Have_Privacy on p.id_post equals hp.id_post
join prv in db.Privacy on hp.id_privacidade equals prv.id_privacidade
where prv.nome = 'Private'
select p.texto;
Tell how it goes
Why not just add a field in Post called isprivate with boolean type of BIT that determines if it's private or not and then use query for provided data with where clause:
var id_posts = from p in db.Posts
where isprivate == false
select p.texto;
If you want to have more than 2 types of privacy and just stick with DB schema you provided, you can go with a JOIN:
If id decides it is private:
var id_posts = from p in db.Posts
join hp in db.Have_Privacy on p.id_post equals hp.id_post
where hp.id_privacidade = 1
select p.texto;
If name decides it is private:
var id_posts = from p in db.Posts
join hp in db.Have_Privacy on p.id_post equals hp.id_post
join prv in db.Privacy on hp.id_privacidade equals prv.id_privacidade
where prv.nome = 'Private'
select p.texto;
Also please note that naming tables in one language and columns in other is considered as bad design. It's hard for others (in this example me) to read it, even if I know what it should mean.
Two last queries use your schema with no changes implemented.

Optimizing query with many-to-many relationship on big data set

I have a database (SQLite) constructed with similar DDL:
CREATE TABLE [Player] (
[PlayerID] INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
[Name] TEXT UNIQUE NULL
);
CREATE TABLE [Position] (
[PlayerID] INTEGER NOT NULL,
[SingleHandID] INTEGER NOT NULL,
[Position] INTEGER NULL,
PRIMARY KEY ([PlayerID],[SingleHandID])
);
CREATE TABLE [SingleHand] (
[SingleHandID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
[Stake] FLOAT NULL,
[Date] DATE NULL,
DataSetID INTEGER NULL
[IsPreflopAllIn] BOOLEAN NULL
);
CREATE UNIQUE INDEX [NameIndex] ON [Player](
[Name] ASC
CREATE INDEX [DataSetIndex] ON [SingleHand](
[DataSetID] ASC
);
It is mapped to Entity Framework model. I am working on large data sets up to 10 million records each.
My problem is, that I need to find all Hands where a specific Player is sting on any given Postion (plus some other filters, like date range).
While I can scan the database very quickly, to find data from a single table, for example:
//[playerIDs and selectedPos are cashed in memory]
context.Positions.Where(p => playerIDs.Contains(p.PlayerID) && selectedPos.Contains(p.Position)).Select(p => p.SingleHandID).Take(maxHands ?? 1);
When I need to do any join between tables, it starts to run very slowly, for example:
//accesing both Position and SingleHand table
context.Positions.Where(p => playerIDs.Contains(p.PlayerID) && selectedPos.Contains(p.Position) && p.SingleHand.DataSetID == dataSetNumber).Select(p => p.SingleHandID).Take(maxHands ?? 1);
What clever trick, comining queries, and code (ex, with local caching) can I pull, to make this run most efficent? I am using System.Data.SQLite provider.
Maybe I should add redundant DataSetID to Position table, then I can do my main query only on the Position table? Later, when I will have the IDs of all the matching hands, it should be quicker to add additional conditions (like date checking)
Create a new index:
CREATE INDEX [DataSetIndex2] ON [SingleHand](
[SingleHandID] ASC,
[DataSetID] ASC
);
That should help a lot.
You can also try something like this:
context.Positions
.Where(p => playerIDs.Contains(p.PlayerID) && SelectedPos.Contains(p.Position))
.Select(p => p.SingleHandID)
.Intersect(context.SingleHand
.Where(s=>s.DataSetId==dataSetNumber)
.Select(s=>s.SingleHandID))
.Take(maxHands ?? 1);

Entity Framework, random query paging

This is what I want to achieve:
I want to query my db to return a list of entities
Randomize the list
Store the IDS of items received for future queries
Run a new query on the same table where the IDs are in the list that I have stored
Order by the list that I have stored.
I have managed to achieve step 1, 2, 3, 4 already but step 5 is difficult. Can anyone help me with a query like so:
SELECT *
FROM table_name
WHERE id IN (1,2,3,4....)
ORDER BY (1,2,3,4....)
Thanks in advance
Try
SELECT table_name.*
FROM crazy_sorted_table
LEFT JOIN
table_name ON crazy_sorted_table.ID=table_name.ID
A normal join (equi join) should do the trick , here is sample approach i tested:
/**crazyOrder filled 100 rows with random value from 1-250 in Id**/
CREATE TABLE [dbo].[crazyOrder] (
[Id] INT NOT NULL,
[Area] VARCHAR (50) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
/**Normal order is filled with value from 1-100 sequentially in id**/
CREATE TABLE [dbo].[normalOrder] (
[Id] INT NOT NULL,
[Name] VARCHAR (50) NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
create table #tempOrder
(id int)
insert into #tempOrder
Select top 10 Id
from crazyOrder
order by NewID()
go
Select n.*
from normalOrder n
join #tempOrder t
on t.id = n.id
I was able to retrieve the rows in the same order as in the temp table (i used a data generator for the values)

Row level permissions for entities based on roles

Using role based permissions, and say each row in a table represents an entity e.g. the Products table, each row represents a Product entity.
How could you provide Product level security based on roles?
e.g. the Sales group has access to Products with ID's 1,234,432,532,34
Multiple roles can be given permissions on any given product.
The goal is to provide an effecient database call for a query like:
var products = ProductDao.GetProductsByRole(234); // roleID = 234
Many-to-Many relations are stored in a separate table:
create table Products(
ProductId int not null identity (1,1),
Name nvarchar(256) not null,
Description nvarchar(max),
constraint PK_Products primary key (ProductId),
constraint UNQ_Products_Name unique (Name));
create table Roles(
RoleId int not null identity (1,1),
Name nvarchar(256) not null,
Description nvarchar(max),
constraint PK_Roles primary key (RoleId),
constraint UNQ_Roles_Name unique (Name));
go
create table ProductRolePermissions (
ProductId int not null,
RoleId int not null,
constraint FK_ProductRolePermissions_Products
foreign key (ProductId)
references Products(ProductId),
constraint FK_ProductRolePermissions_roles
foreign key (RoleId)
references Roles(RoleId));
go
create unique clustered index CDX_ProductRolePermissions
on ProductRolePermissions (RoleId, ProductId);
create unique nonclustered index NDX_ProductRolePermissions
on ProductRolePermissions (ProductId, RoleId);
go
create function dbo.GetProductsByRole( #roleId int)
returns table
with schemabinding
as return (
select ProductId
from dbo.ProductRolePermissions
where RoleId = #roleId);
go
insert into Products (Name)
values ('P1'), ('P2'), ('P3'), ('P4');
insert into Roles (Name)
values ('G1'), ('G2');
insert into ProductRolePermissions (ProductId, RoleId)
values (1,1), (3,1), (2,2), (3,2);
go
select 'Products permitted for G1', p.*
from dbo.GetProductsByRole(1) r
join Products p on r.ProductId = p.ProductId;
select 'Products permitted for G2', p.*
from dbo.GetProductsByRole(2) r
join Products p on r.ProductId = p.ProductId;
Things get a little more complicated if you want to follow the classical grant/deny/revoke permission model for read/write/full access with multiple role memberships.

Categories