Check if a table exists within a database using LINQ - c#

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.

Related

Entity Framework Core migrations on multiple databases

I am creating a .NET 6 API using EF core with SQL Server, and trying to implement multiple databases that would different connection strings based on an id that is passed in from an identity token. Essentially, there would be multiple databases that each contain the same tables, but storing different information. A user would be able to edit data from the front end and based on the "tenant" that they are working in, it would store the data in the appropriate database. For the time being, all of the databases will be on the same server.
When a user makes a request, I have been able to implement that correctly, using the following inside of my DbContext:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(!optionsBuilder.IsConfigured)
{
var tenantId = _httpContext.GetClaim("tenantId");
optionsBuilder.UseSqlServer(GetClientConnectionString(tenantId));
}
}
where the GetClientConnectionString function would perform some logic to manipulate the default connection string and return the correct value based on that id from the user token. This works fine and when run with the appropriate token, is able to switch connection strings ok.
The part I am unsure of is how to maintain each database individually - is there a good way to run migrations for each of the databases? I know that a connection string can be passed in to the dotnet ef migrations ... command, however if the amount of databases grows to a decent number this does not seem efficient.
Is the best bet to just bite the bullet and perform the migrations manually? Or would it be possible to somehow loop through a collection of keys which would return the connection string value and apply the migration to each?
I am relatively new to anything more than a simple dataset in EF so I am hoping I am not just missing something.
Thanks in advance
I have never done this, but You can try start migration for all dbs with one DbContext(said that the bases are the same), just try to change the connection string for each db. I don't have time to test it, but I hope it will work
foreach (var connection in ConnectionList)
{
var optionBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionBuilder.UseSqlServer(connection);
using var dbContext = new ApplicationDbContext(optionBuilder.Options);
var pendingMigrations = await dbContext.Database.GetPendingMigrationsAsync();
if(pendingMigrations.Any())
{
await dbContext.Database.MigrateAsync();
}
await dbContext.DisposeAsync();
}

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.

Prevent duplicate entries in database during POST call

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.

How to get exactly all users from database

In ASP.NET boilerplate project.
I have a database with users. I am using standard methods for retrieving them _userRepository.GetAll() and deleting _userRepository.Delete(id).
By default when user id deleted it is still kept in database with isDeleted
field marked as true.
My question is: is there in ABP any default method that retrieves exactly all users from database, and what follows:
is there any other possibility to do this than writing stored procedure like
SELECT * FROM dbo.AbpUsers
and using it in repository (and then in service)?
You should disable filter(SoftDelete):
//provided by DI
private readonly IUnitOfWorkManager _unitOfWorkManager;
using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
//or
//using (CurrentUnitOfWork.DisableFilter(AbpDataFilters.SoftDelete))
{
var completelyAllUsers = _userRepository.GetAllList();
}

How to configure ASMX web service URL from remote source

We are working on a legacy C# enterprise app. Its client uses several web services, whose URLs, among lots of other settings, are read from the local app.config file. We want to migrate these settings into a global DB to simplify their management. However, I can't figure out how (and whether) it is possible to migrate the web service URLs. These are read from the service client code generated by VS and I can't seem to find a way to tell VS to use a different settings provider than the one generated into Settings.Designer.cs .
We can overwrite the service facade's Url property with the value we want, after it is created - this is the solution currently used in several places in the code. However, I wouldn't like to touch every part of our codebase where any of these services is used (now and in the future). Even less would I like to modify generated code.
There has to be a better, cleaner, safer solution - or is there?
Btw our app runs on .NET 2.0 and we won't migrate to newer versions of the platform in the foreseeable future.
The Refernce.cs file that is generated by the Visual Studio indicates that the URL of the webservice will be retrieved from the settings:
this.Url = global::ConsoleApplication1.Properties.
Settings.Default.ConsoleApplication1_net_webservicex_www_BarCode;
I believe that John Saunders gave you a wonderful suggestion in his comment. You need a SettingsProvider class which:
...defines the mechanism for storing configuration data used in the
application settings architecture. The .NET Framework contains a
single default settings provider, LocalFileSettingsProvider, which
stores configuration data to the local file system. However, you can
create alternate storage mechanisms by deriving from the abstract
SettingsProvider class. The provider that a wrapper class uses is
determined by decorating the wrapper class with the
SettingsProviderAttribute. If this attribute is not provided, the
default, LocalFileSettingsProvider, is used.
I don't know how much you have progressed following this approach, but it should go pretty straighforward:
Create the SettingsProvider class:
namespace MySettings.Providers
{
Dictionary<string, object> _mySettings;
class MySettingsProvider : SettingsProvider
{
// Implement the constructor, override Name, Initialize,
// ApplicationName, SetPropertyValues and GetPropertyValues (see step 3 below)
//
// In the constructor, you probably might want to initialize the _mySettings
// dictionary and load the custom configuration into it.
// Probably you don't want make calls to the database each time
// you want to read a setting's value
}
}
Extend the class definition for the project's YourProjectName.Properties.Settings partial class and decorate it with the SettingsProviderAttribute:
[System.Configuration.SettingsProvider(typeof(MySettings.Providers.MySettingsProvider))]
internal sealed partial class Settings
{
//
}
In the overridden GetPropertyValues method, you have to get the mapped value from the _mySettings dictionary:
public override SettingsPropertyValueCollection GetPropertyValues(
SettingsContext context,
SettingsPropertyCollection collection)
{
var spvc = new SettingsPropertyValueCollection();
foreach (SettingsProperty item in collection)
{
var sp = new SettingsProperty(item);
var spv = new SettingsPropertyValue(item);
spv.SerializedValue = _mySettings[item.Name];
spv.PropertyValue = _mySettings[item.Name];
spvc.Add(spv);
}
return spvc;
}
As you can see in the code, in order to do that, you need to know the setting name as it was added in the app.config and the Settings.settings when you have added the reference to the web service (ConsoleApplication1_net_webservicex_www_BarCode):
<applicationSettings>
<ConsoleApplication30.Properties.Settings>
<setting name="ConsoleApplication1_net_webservicex_www_BarCode"
serializeAs="String">
<value>http://www.webservicex.net/genericbarcode.asmx</value>
</setting>
</ConsoleApplication30.Properties.Settings>
</applicationSettings>
This is a very simple example, but you might use a more complex object to store the configuration information in conjunction with other properties available in the context such as item.Attributes or context in order to get the proper configuration value.

Categories

Resources