Row level permissions for entities based on roles - c#

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.

Related

Foreign key problem when saving data to SQL Server using Entity Framework

C# code and database view
Here is my database schema:
CREATE TABLE University
(
UniversityID int NOT NULL IDENTITY,
UniversityName nvarchar(100) NOT NULL,
PRIMARY KEY (UniversityID)
)
CREATE TABLE Enstitute
(
EnstituteID int NOT NULL IDENTITY,
UniversityID int NOT NULL,
EnstituteName nvarchar(100),
PRIMARY KEY (EnstituteID),
FOREIGN KEY (UniversityID) REFERENCES University(UniversityID)
)
Part of the Entity Framework code:
en.EnstituteName = enstituteBox.Text; //entitute name input
uni.UniversityName = universityBox.Text; //university name input
db.Enstitute.Add(en);
db.University.Add(uni);
db.SaveChanges();
My problem is, when I add data that the user inputs, I write the same university name but in the database records the same university names saved with different UniversityID. 1 enstitute has 1 university and 1 university have more than one enstitute. When I select a university, if the university is selected before, I want save to same UniversityID (foreign key) to Enstitute table, not a new Foreign key ID. Can someone help me?
As far as I understood the problem is that you are adding the university even if already exist. Your PK is pointing to the ID, so the name is not part of it. Probably you need to specify a double PK or check if the university name exist already.
Try this
var university = await db.University.FirstOrDefaultAsync(x =>x.UniversityName == uni.UniversityName);
if(university == null){
//your code
}
else
{
en.UniversityId = university.UniversityId;
db.Enstitude.Add(en);
}
await db.SaveChangesAsync();

How do I update a table that only has foreign keys

I have a database with a table with only foreign keys. I am able to manually add data into this table, and then remove it with code. However, I am unable to add data to it. This may be because I don't actually know how to access my table, or I'm doing it wrong. Help is appreciated.
My foreign key only table:
CREATE TABLE UserCategory
(
CatID INT FOREIGN KEY REFERENCES Category(ID),
UserID NVARCHAR(128) FOREIGN KEY REFERENCES [User](ID),
CONSTRAINT PK_UserCategory PRIMARY KEY (CatID, UserID)
);
The code that links it in the model:
modelBuilder.Entity<Category>()
.HasMany(e => e.Users)
.WithMany(e => e.Categories)
.Map(m => m.ToTable("UserCategory").MapLeftKey("CatID").MapRightKey("UserID"));
As you can see, I have a table called what I created, and with the values expected.
However, when I try to access this table through code, I cannot access those values directly. I can delete values like this:
var userCats = thisUser.Categories.ToList();
foreach (var cat in userCats)
{
thisUser.Categories.Remove(cat);
}
But when I try and access the UserCategories properties, I get this:
What can I do to add new UserCategories to my database? If I just create a new Category, and add that the same way I delete one (but with Add, obviously) I am required to fill out Name. Then when I fill out Name, it tells me there's a key conflict, because it appears to be adding it to the Category table, not the UserCategory.
Here are the other two tables in question:
CREATE TABLE [User]
(
ID NVARCHAR(128) PRIMARY KEY,
FirstName VARCHAR(255) NOT NULL,
LastName VARCHAR(255),
JoinDate DATETIME,
ZipCode VARCHAR(25),
SearchRadius INT,
LoginToBusinessSide BIT
);
/*The categories businesses can fall under. */
CREATE TABLE Category
(
ID INT IDENTITY(1,1) PRIMARY KEY,
[Name] VARCHAR(255) UNIQUE NOT NULL,
);
Am I trying to access it wrong? How can I access it correctly? Thank you.
I'm not totally sure why this works, but I was able to finally save entries into my UserCategory table. This is my code:
for(var i = 0; i < categories.Length; ++i)
{
var user = db.Users.Find(thisUser.ID);
var cat = db.Categories.Find(categories[i]);
user.Categories.Add(cat);
}
I have to an active connection to both tables that the foreign keys are linked to, and then when I add one to the Categories table, it goes to the correct place.
If you understand exactly why this works, or if there's a better way, please do comment.

Fast Way to Replace Names with Ids in Datatable?

I have a very large CSV file I have to load on a regular basis that contains time series data. Examples of the headers are below:
| SiteName | Company | Date | ResponseTime | Clicks |
This data comes from a service external to the uploader. SiteName and Company are both string fields. In the database these are normalized. There is a Site table and a Company table:
CREATE TABLE [dbo].[Site] (
[Id] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[Name] NVARCHAR(MAX) NOT NULL
)
CREATE TABLE [dbo].[Company] (
[Id] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[Name] NVARCHAR(MAX) NOT NULL
)
As well as the data table.
CREATE TABLE [dbo].[SiteStatistics] (
[Id] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[CompanyId] INT NOT NULL,
[SiteId] INT NOT NULL,
[DataTime] DATETIME NOT NULL,
CONSTRAINT [SiteStatisticsToSite_FK] FOREIGN KEY ([SiteId]) REFERENCES [Site]([Id]),
CONSTRAINT [SiteStatisticsToCompany_FK] FOREIGN KEY ([CompanyId]) REFERENCES [Company]([Id])
)
At around 2 million rows in the CSV file any sort of IO-bound iteration isn't going to work. I need this done in minutes, not days.
My initial thought is that I could pre-load Site and Company into DataTables. I already have the CSV loaded into a datatable in the format that matches the CSV columns. I need to now replace every SiteName with the Id field of Site and every Company with the Id field of Company. What is the quickest, most efficient way to handle this?
If you go with Pre-Loading the Sites and Company's you can get the distinct values using code:
DataView view = new DataView(table);
DataTable distinctCompanyValues = view.ToTable(true, "Company")
DataView view = new DataView(table);
DataTable distinctSiteValues = view.ToTable(true, "Site")
Then load those two DataTables into their SQL Tables using Sql-Bulk-Copy.
Next dump all the data in:
CREATE TABLE [dbo].[SiteStatistics] (
[Id] INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
[CompanyId] INT DEFAULT 0,
[SiteId] INT DEFAULT 0,
[Company] NVARCHAR(MAX) NOT NULL,
[Site] NVARCHAR(MAX) NOT NULL,
[DataTime] DATETIME NOT NULL
)
Then do an UPDATE to set the Referential Integrity fields:
UPDATE [SiteStatistics] ss SET
[CompanyId] = (SELECT Id FROM [Company] c Where ss.[Company] = c.Name),
[SiteId] = (SELECT Id FROM [Site] s Where ss.[Site] = s.Name)
Add the Foreign Key constraints:
ALTER TABLE [SiteStatistics] ADD CONSTRAINT [SiteStatisticsToSite_FK] FOREIGN KEY ([SiteId]) REFERENCES [Site]([Id])
ALTER TABLE [SiteStatistics] ADD CONSTRAINT [SiteStatisticsToCompany_FK] FOREIGN KEY ([CompanyId]) REFERENCES [Company]([Id])
Finally delete the Site & Company name fields from SiteStatistics:
ALTER TABLE [SiteStatistics] DROP COLUMN [Company];
ALTER TABLE [SiteStatistics] DROP COLUMN [Site];

LINQ Expression for CROSS APPLY two levels deep

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.

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)

Categories