Prevent duplicate entries in database during POST call - c#

I have an API written in Asp Net Core 2.1 (upgraded from 2.0) which build an entity from a DTO sent by a mobile app.
The DTO have a field "mobileId" (Guid format) to prevent the mobile to send the same object when it goes online (after connectivity issues for example).
But this solution does not seem to be efficient as presented below :
There are 4 lines whereas I actually wanted only 1 line :S I don't understand how it occurred because I specified in the Startup:
services.AddScoped<DbContext>(s =>
{
// code emitted for brevity
});
The code of the API itself is centralized in a Handler because our API follow a little piece of CQRS pattern and the "Dispatcher" is registered via Autofac :
public class DispatcherModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<Dispatcher>()
.As<IDispatcher>()
.InstancePerDependency();
}
}
The same applies for IUnitOfWork which use our scoped DbContext internally
At the beginning I check that an entity with the same 'mobileId' is not already present in database :
if (dto.MobileId != null)
{
Report existingMobileId = await UnitOfWork.Repository<Report>()
.GetFirstOrDefaultAsync(qr => qr.MobileId == dto.MobileId);
if (existingMobileId != null)
{
return new Result<object>(new Error(400, $"Report with '{dto.MobileId}' already exists in DB."));
}
}
What do you think I'm doing wrong ? Or maybe I should add something else ?
Thank you for your help guyz :)
Technical environment :
- ASP.NET Core 2.1
- Entity Framework Core
- Azure SQL Database

AddScoped means that new instance of service is created for each request, so, if you have several simultaneous requests, you may have some kind of race condition on repository level when each checks presence of a row before writing it to db. I would recommend putting this responsibility on the database level applying Unique constraint on mobileId column.

Related

Global Filters on a Multitenancy Application with EF Core 6 + GraphQL on a single database approach

how have you been?
Can someone help us with these multi-tenancy questions and best practices with EF Core 6.0?
We are working on a multi-tenancy application exposing data through a GraphQL endpoint. We are running with .NET 5.0, EF Core 6, GraphQL and SQL Server.
Our setup is running fine. There are more than 45 tenants on this solution, and each Tenant has their Web Application that connects on a single GraphQL endpoint. The only thing that changes for each Tenant is the data, that is logically separated by a "TENANT_ID" column.
The approach and pattern that we decided for use in this project is based on a shared database with no schema customization (only dbo). In resume: One database, one schema, one GraphQL endpoint and multiple websites consuming these services. Websites that connect on the GraphQL need to pass a JWT and a Tenant ID. This field "TenantID" passed in the header is used for allowing the filter on the server side.
Example: Advertises.Where(a => a.TenantID == x);
We are studying the best practices to filter the data based on a TENANT_ID passed from the client to the server using some HTTP headers.
EF Core 6 has the Global Query Filters, but it seems is not possible to apply the filter, because the TenantID changes for every requisition.
The EF Core OnModelCreating method is always called once per AppDomain per DbContext, but we need to change this value for every request.
Does anyone recommend an approach to apply EF Query Filters using a external ID came from a http request?
In our research, we found some tips to inject the IHttpContextAccessor on the database layer (thus, to retrieve the headers, and apply the filters based on the TenantID), but, I confess that I'm not confortable for using the AspNetCore.Http namespace on the database layer.
Thank you all.
Claims are the way.
After some research, we found out that through the query type operations, it's possible to receive a ClaimsPrincipal object, a class from the System.Security.Claims namespace.
Therefore, it is possible to send some HTTP headers that contain your Tenant ID. This value could be your KEY for filtering. In my case, that was a Guid type.
In this sample, we are passing an object called "claims" which is a ClaimsPrincipal.
[Authorize(Policy = "YourPolicyName"), UseDbContext(typeof(YourDatabaseContext)), UseOffsetPaging, UseFiltering, UseSorting]
public IQueryable<User> Users(ClaimsPrincipal claims, [Service] YourDatabaseContext context)
{
var tenantID = claims.Get(ClaimTypes.GroupSid);
return context.Users.Where(u => u.TenantID == tenantID);
}
After that, we just needed to create a extension method to retrieve data from the Claim:
public static class ClaimsExtensions
{
public static Guid Get(this ClaimsPrincipal claimsPrincipal, string claimType)
{
var element = claimsPrincipal.FindFirstValue(claimType);
if (element is not null)
{
if (Guid.TryParse(element, out Guid id))
return id;
}
return Guid.Empty;
}
}
That's it. Don't forget it is needed to pass the SID on your HTTP Request.

How to use database sharding with EF Core and C#"

I'm currently in the process of converting my 6 years old C# application to .NET Core v3 and EF Core (and also using Blazor).
Most of it is working except for the Sharding part.
Our application creates a new database for each client. We use more or less this code for it: https://learn.microsoft.com/en-us/azure/sql-database/sql-database-elastic-scale-use-entity-framework-applications-visual-studio
I'm now trying to convert it to EF Core, but get stuck at this part:
// C'tor to deploy schema and migrations to a new shard
protected internal TenantContext(string connectionString)
: base(SetInitializerForConnection(connectionString))
{
}
// Only static methods are allowed in calls into base class c'tors
private static string SetInitializerForConnection(string connnectionString)
{
// We want existence checks so that the schema can get deployed
Database.SetInitializer<TenantContext<T>>(new CreateDatabaseIfNotExists<TenantContext<T>>());
return connnectionString;
}
// C'tor for data dependent routing. This call will open a validated connection routed to the proper
// shard by the shard map manager. Note that the base class c'tor call will fail for an open connection
// if migrations need to be done and SQL credentials are used. This is the reason for the
// separation of c'tors into the DDR case (this c'tor) and the internal c'tor for new shards.
public TenantContext(ShardMap shardMap, T shardingKey, string connectionStr)
: base(CreateDDRConnection(shardMap, shardingKey, connectionStr), true /* contextOwnsConnection */)
{
}
// Only static methods are allowed in calls into base class c'tors
private static DbConnection CreateDDRConnection(ShardMap shardMap, T shardingKey, string connectionStr)
{
// No initialization
Database.SetInitializer<TenantContext<T>>(null);
// Ask shard map to broker a validated connection for the given key
var conn = shardMap.OpenConnectionForKey<T>(shardingKey, connectionStr, ConnectionOptions.Validate);
return conn;
}
The above code doesn't compile because the Database object doesn't exist in this way in EF Core.
I assume I can simplify it using TenantContext.Database.EnsureCreated(); somewhere. But I can't figure out how to modify the methods, which to remove, which to change (and how).
Of course, I've been searching for an example using sharding and EF Core but couldn't find it.
Does anybody here has done this before in EF Core and is willing the share?
I'm specifically looking for what to put in startup.cs and how to create a new sharding/database when I create a new client.
In EF.Core just resolve the shard in OnConfiguring. EG
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var con = GetTenantConnection(this.tenantName);
optionsBuilder.UseSqlServer(con,o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
Note that if you have a service or factory that returns open DbConnections, then you'll need to Close()/Dispose() them in the DbContext.Dispose(). If you get a connection string or a closed connection then DbContext will take care of closing the connection.
ASP.NET Core best-practices probably call for injecting an ITenantConfiguration service or somesuch in your DbContext. But the pattern is the same. Just save the injected service instance to a DbContext field and use it in OnConfiguring.
With the app that I'm working on, the desired shard is not discoverable until request time (for example, knowing what user is making the request, and then routing that user to their database). This meant that the OnConfiguring solution proposed above was not viable.
I worked around this by using IDbContextFactory<TContext>, and defining an extension on top of it, which sets the connection string based on whatever you want. I believe the database connection is created lazily in EF, and you are able to set the connection string up until the EF first needs to actually connect to the database.
In my case, it looked something like this:
var dbContext = _dbContextFactory.CreateDbContext();
var connectionString = $"DataSource={_sqlliteDirectory}/tenant_{tenant.TenantId}.db";
dbContext.Database.SetConnectionString(connectionString);
The downside is that it breaks the database abstraction (this code knows that my database is a local sqllite instance). An abstraction was not necessary in this layer of my app, but it is something very solvable if it's required.

IdentityServer 4, Create Panel to CRUD Clients

Currently I Have configured Identityserver4 as separated project + My WebAPI and store in DB Credentials in IdentityServer.
Now i have problem how to make CRUD(In my frontend API) to IdentityServer(I want from my API add Clients to IdentityServer)
How to make property?
From IdentityServer4.EntityFramework and IdentityServer4.EntityFramework.Storage, you have access to IConfigurationDbContext (once you've added the required services in ConfigureServices using e.g. AddConfigurationStore). Because this is registered as part of the Dependency Injection system, you can take a dependency on it in one of your controllers. e.g.:
public class ClientsController : ControllerBase
{
private readonly IConfigurationDbContext _configurationDbContext;
public ClientsController(IConfigurationDbContext configurationDbContext)
{
_configurationDbContext = configurationDbContext;
}
// ...
}
IConfigurationDbContext is an abstraction of a standard DbContext, with the following DbSet<T> properties:
Clients
IdentityResources
ApiResources
It also includes both SaveChanges and SaveChangesAsync - Everything one might expect from a DbContext. Because of all of this, you can CRUD each of these entities just like any other Entity Framework Core driven database.
One final thing to note is that there are both Models (in IdentityServer4.Storage) and Entities (in IdentityServer4.EntityFramework.Storage). There are also a few extension methods for mapping between these (e.g. ClientMappers.ToEntity).
Given all of this, you can create a Model inside of your controller (or perhaps somewhere much better encapsulated than directly there). Here's a basic example for creating a new Client:
var clientModel = new Client
{
ClientId = "",
ClientName = "",
// ...
};
_configurationDbContext.Clients.Add(clientModel.ToEntity());
await _configurationDbContext.SaveChangesAsync();
The Client class here comes from IdentityServer4.Models and is then converted to an Entity using a ToEntity extension method I hinted at above. Working with a Model and converting to an Entity is simpler than trying to manipulate an Entity directly - If you're interested, you can see the mapping that takes place here.
This works in the same way for ApiResources, IdentityResources, etc. Use the source code links I've provided if you want to find out more about those specifically, but the information I've provided here should have you covered.
In order to use IdentityServer4 and IdentityServer4.EntityFramework in your API project, you can just add the two references to your API project. After that, you can configure the DI in the same way (using AddIdentityServer in ConfigureServices), but you don't need to add the middleware (using UseIdentityServer in Configure). You can even just use AddIdentityServer().AddConfigurationStore(...) to set up the relevant services, as you don't need a signing key, etc.
One way you can do this is by bootstrapping the ID4 Quickstart (tutorial located here):
http://docs.identityserver.io/en/release/quickstarts/3_interactive_login.html
Other option is to use their quickstart seeds located here to speed this up:
https://github.com/IdentityServer/IdentityServer4.Samples
Now if you want to implement restfull login there are constraints around it (i wanted to find out as well) check out this question:
IdentityServer 4 Restfull Login/Logout

Asp.Net 5 / Identity 3: Caching of Claims in the IdentityDbContext implementation

While looking for a way to be able to assign and revoke roles via an admin controller for users other than the one making a request, I've implemented a custom IAuthorizeFilter that checks if Guid tag, stored as a Claim, matches to a value in the Entity Framework 7 Code First Identity table for UserClaims.
Essentials, it's this code:
public class RefreshUserClaimsFilterAttribute : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext context)
{
var User = context.HttpContext.User;
var dbContext = context.HttpContext.ApplicationServices.GetRequiredService<ApplicationDbContext>();
var stampFromClaims = User.Claims.FirstOrDefault(Claim => Claim.Type == "ClaimsStamp")?.Value;
var stampFromDb = dbContext.UserClaims.Where(UserClaim => UserClaim.UserId == User.GetUserId()).ToList().FirstOrDefault(UserClaim => UserClaim.ClaimType == "ClaimsStamp")?.ClaimValue;
// Update claims via RefreshSignIn if necessary
}
}
I'm having the problem at the line where I'm assigning var stampFromDb, it could be much more readable in the following way:
var stampFromDb = dbContext.UserClaims.FirstOrDefault(UserClaim => UserClaim.UserId == User.GetUserId() && UserClaim.ClaimType == "ClaimsStamp")?.ClaimValue;
That, however, gives me cached (the same values as the actual claims from User.Identity) results and I could not find any documentation on this. My best guess is that the error is somewhere on my side, but I've never encountered such a problem before. This is the first time I'm using Asp.Net 5 and EF7. I'm using the default connection (LocalDB) to SQL Server 12.0.2000.
Is this a feature and, if yes, can it be turned off or did I make a mistake somewhere?
The issue was caused due to there being two different ways to create a service via dependency injection:
The sample code in my question used
var dbContext = context.HttpContext.ApplicationServices.GetRequiredService<ApplicationDbContext>();
where it should use
var dbContext = context.HttpContext.RequestServices.GetRequiredService<ApplicationDbContext>();
The difference here is between ApplicationServices and RequestServices. It looks like the ApplicationServices injector does have an instance of the database context somewhere which has had the DbSet filled earlier and therefore returning cached data instead of doing a database query.

Check if a table exists within a database using LINQ

We have a database that has been deployed to various clients. We are currently introducing a optional new feature that, to be used, will require the customers who want the feature to have a new table added to the existing database.
As we are rolling out a new piece of software that will have to interact with versions of the database both with and without the new table (and as we don't want 2 versions one for customers who have the new table and one for ones who don't) we were wondering if it is possible to programmatically determine (with entity framework) whether a table exists in the database (I can try to access the table and have it throw a exception but was wondering if there was a built in function to do this)
Thanks
Edit: Given that people are telling me i should be using a config file not checking with EF can anyone give me guidence on how to check the config file with, for example, a custom data annotations for a mvc controller. Something like:
[Boolean(Properties.Settings.Default.TableExists)]
public class NamedController : Controller
Which throws a page not found if false?
Edit 2: With the Suggestions given by people to use the config settings i ended up with the following solution
App settings to set whether the table exists
<appSettings>
<add key="tableExists" value="True"/>
</appSettings>
a custom data annotation to say whether to allow access to controller
[AuthoriseIfTableExistsIsTrue]
public class NamedController : Controller
the code for the custom authorise
public class AuthoriseIfTableExistsIsTrue : AuthorizeAttribute
{
private readonly bool _tableExists;
public AuthoriseIfTableExistsIsTrue()
{
_tableExists = string.Equals(bool.TrueString, ConfigurationManager.AppSettings["tableExists"], StringComparison.InvariantCultureIgnoreCase);
}
public AuthoriseIfTableExistsIsTrue(bool authorise)
{
_tableExists = authorise;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
if (_tableExists)
return base.AuthorizeCore(httpContext);
else
throw new HttpException(404, "HTTP/1.1 404 Not Found");
}
}
Thanks everyone for the help and telling me not to use EF for this and use config setting instead
A much better option would be to store the version differences as configuration. This could be stored in the database itself, a configuration file or even web.config.
Otherwise you'll end up with messy code like:
int result = entity.ExecuteStoreQuery<int>(#"
IF EXISTS (SELECT * FROM sys.tables WHERE name = 'TableName')
SELECT 1
ELSE
SELECT 0
").SingleOrDefault();
The only possible ways are
Query table and get exception
Use native SQL to query system views and look for that table - in EFv4 you can execute query directly from ObjectContext by calling ExecuteStoreQuery.
Your entity model will still have this table so in my opinion you should simply ship your DB with that table and in application code handle if feature is allowed or not (table will not be used but will be in DB).
If you want to make modular system then whole your feature (including application code) should not be present when client don't want to use it.

Categories

Resources