Connection string not correct on EF 6 Azure Function - c#

I have a very simple Azure function which updates rows on a database.
This works fine locally via Postman.
This is the very simple call I have in my Azure function
string connectionString = ConfigurationManager.ConnectionStrings["CatsDBEntities"].ConnectionString;
using (var context = new CatsDBEntities(connectionString))
{
// using (var db = new CatsDBEntities())
{
Cat cat = new Cat
{
Name = "BengA",
Id = id
};
context.Cats.Add(cat);
context.SaveChanges();
}
response = req.CreateResponse(HttpStatusCode.OK);
}
}
catch (System.Exception)
And here is my context
public CatsDBEntities(string connectionString) : base(connectionString) { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Cat> Cats { get; set; }
Here is my connection string in local settings (I know this doesn't matter when deploying to Azure but I am using this as an example of my connection string):
metadata=res://*/CatModel.csdl|res://*/CatModel.ssdl|res://*/CatModel.msl;provider=System.Data.SqlClient;provider connection string='data source=x.database.windows.net;initial catalog=CatsDB;user id=x;password=!;MultipleActiveResultSets=True;App=EntityFramework'",
and within Azure, I am entering my connection string like this:
This works great locally through Postman get a 200 but when deployed to Azure I get a 500 and this is my latest error:
This is error means nothing anyway because I have tried several times now and I keep changing the string and the error sometimes changes and sometimes not.
I have added the EF6 database first project as a separate project to my Azure function and I have put a reference between the two projects.

As you mentioned in the post that EF6 DataBase is added first then,
This is caused by how EF model first connection strings are generated.
The EF connection string builder requires a plain connection string in the constructor.
You can Refer this SO thread link for more information.

Related

EF6: DbContext.Database.Connection.ConnectionString changes after calling ExecuteSqlCommand

I am observing a weird behavior in EF6 DbContext. My custom DbContext class reads its connection string from my App.config:
public partial class WorkobjectContext : DbContext
{
public WorkobjectContext()
: base("name=MainConnectionString")
{
// Set the initializer to null to disable intializtion otherwise EF tries to create the table if it does not exist (CreateDatabaseIfNotExists)
Database.SetInitializer<WorkobjectContext>(null);
}
}
As I am using the same connection string for my Ado.NET functions, I try to read the connection string out of DbContext:
var connectionstring = this.MyContext.Database.Connection.ConnectionString;
But now I am observing that this.MyContext.Database.Connection.ConnectionString changes as I call ExecuteCommand:
...
Console.WriteLine(this.WorkobjectContext.Database.Connection.ConnectionString);
// Output: DATA SOURCE=XXXX:1000/XXXX.XXXX;PASSWORD=MyPassword;USER ID=MyUserId
var ret = this.WorkobjectContext.Database.ExecuteSqlCommand(
"BEGIN WPK_Inbox.Check_WOB_User(:pn_UserID, :pn_WOBID, :pn_Return, :pvErrorMsg, :pnErrorCode); end;",
new object[]
{
userIdParameter,wobIdParameter,returnValueParameter,errorMsgParameter,errorCodeParameter
}
);
Console.WriteLine(this.WorkobjectContext.Database.Connection.ConnectionString);
// Output: DATA SOURCE=XXXX:1000/XXXX.XXXX;PUSER ID=MyUserId
As you can see, after calling ExecuteSqlCommand the connection string changes and the password part disappears.
Why is this happening? Is this a bug?

EF Core - Create a migration without connection string

I have been ranging across multiple questions, tutorials and examples for something that fits this problem.
What if I don't know my connection string at the time I want to create my first initial migration? Given I am given the opportunity to set the connection string at the time of instantiating context eg:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
As described in the docs..
If you need to have a connection string to run migrations then for those situations where you don't have one (Tenant database where you get a connection string from a user account) how do you run an initial migration??
Do you create a dummy connection string to create the migration.. seems dodgy to me. I would love some input on this.
You can implement IDesignTimeDbContextFactory that will be used instead your host builder in the design time. UseSqlServer now has a parameterless overload.
It will look like this and has to be in the same assembly as the db context or in the startup project:
public class MyAppDbContextFactory: IDesignTimeDbContextFactory<MyAppDbContext>
{
public MyAppDbContext CreateDbContext(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<MyAppDbContext>();
optionsBuilder.UseSqlServer();
return new MyAppDbContext(optionsBuilder.Options);
}
}
More details can be found here:
https://learn.microsoft.com/en-us/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli#from-a-design-time-factory

MVC: EF6 Connection Pooling and SQL Server CONTEXT_INFO

In an ASP.NET MVC application, I'm trying to use SQL Server's CONTEXT_INFO to pass the currently logged in user so my audit triggers record not only the web server login, but also the login of the site.
I'm having trouble being certain that the current user will always be fed into the database server context though.
On the backend I have everything set up, a sproc to set the context, a function to pull it and DML triggers to record, no problem.
The app end is a bit more involved. I subscribe to the Database.Connection.StateChange event so I can catch each newly opened connection and set this context accordingly.
Additionally, to be able to retrieve the current login ID of the MVC site in the data layer (which has no access to the web project), I supply a delegate to the EF constructor that will return the user ID. This also means that any other peripheral projects I have set up require this dependency as well, and it keeps most of the implementation detail out of my hair during the web dev:
public class CoreContext : DbContext
{
Func<int> _uidObtainer;
public CoreContext(Func<int> uidObtainer) : base(nameof(CoreContext)) { construct(uidObtainer); }
public CoreContext(Func<int> uidObtainer, string connection) : base(connection) { construct(uidObtainer); }
void construct(Func<int> uidObtainer) {
// disallow updates of the db from our models
Database.SetInitializer<CoreContext>(null);
// catch the connection change so we can update for our userID
_uidObtainer = uidObtainer;
Database.Connection.StateChange += connectionStateChanged;
}
private void connectionStateChanged(object sender, System.Data.StateChangeEventArgs e) {
// set our context info for logging
if (e.OriginalState == System.Data.ConnectionState.Open ||
e.CurrentState != System.Data.ConnectionState.Open) {
return;
}
int uid = _uidObtainer();
var conn = ((System.Data.Entity.Core.EntityClient.EntityConnection)sender).StoreConnection;
var cmd = conn.CreateCommand();
cmd.CommandText = "audit.SetContext";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("#DomainUserID", uid));
cmd.ExecuteNonQuery();
}
// etc etc...
In my MVC project, I'll have code that looks like this:
context = new Data.CoreContext(() => AppService.UserID());
(making use of a readily accessible method to pass as delegate, which in turn reads from HttpContext.Current.User)
This is all shaping up nicely, except one unknown:
I know that it's possible for a EF Context instance to span multiple logged in users as this lives as part of the IIS app pool and not per HttpContext
What I don't know is enough about connection pooling and how connections are opened/re-opened to be safe in knowing that for each time my StateChange handler runs, I'll actually be retrieving the new UserID from the delegate.
Said differently: is it possible for a single connection to be open and used over the span of two separate HttpContext instances? I believe yes, seeing as how there's nothing to enforce otherwise (at least not that I'm aware of).
What can I do to ensure that each connection is getting the current HttpContext?
(possibly pertinent notes: There's no UoW/Repository pattern outside of EF itself, and data contexts are generally instantiated once per controller)
I see: the one context per controller is generally incorrect. Instead I should be using one context per request, which (besides other advantages), ensures my scenario operates correctly as well.
I found this answer, which explains the reasoning behind it: One DbContext per web request... why?
And I found this answer, which explains quite succinctly how to implement via BeginRequest and EndRequest: One DbContext per request in ASP.NET MVC (without IOC container)
(code from second answer pasted below to prevent linkrot)
protected virtual void Application_BeginRequest()
{
HttpContext.Current.Items["_EntityContext"] = new EntityContext();
}
protected virtual void Application_EndRequest()
{
var entityContext = HttpContext.Current.Items["_EntityContext"] as EntityContext;
if (entityContext != null)
entityContext.Dispose();
}
And in your EntityContext class...
public class EntityContext
{
public static EntityContext Current
{
get { return HttpContext.Current.Items["_EntityContext"] as EntityContext; }
}
}

Trouble inserting into Azure db from Windows Store app

I'm currently working on a Windows Store app (for a school assignment), and I'm having trouble inserting data into my database which is stored in Azure. Whenever I attempt to insert data into the db, the MobileServiceInvalidOperationException gets thrown. My code is as follows:
In my model class
class Division
{
public string Id {get; set;}
[JsonProperty(PropertyName = "divisionTitle")]
public string DivisionTitle {get; set;}
}
And the relevant code in my MainPage.xaml.cs file
private MobileServiceCollection<Division, Division> divisionItems;
private IMobileServiceTable<Division> divisionTable = App.MobileService.GetTable<Division>();
private async void InsertDivision(Division divisionItem)
{
// This code inserts a new division Item into the database.
// When the operation completes and Mobile Services has
// assigned an Id, the item is added to the collection
try
{
await divisionTable.InsertAsync(divisionItem);
divisionItems.Add(divisionItem);
}
/////////////////////////////////////////////////////////
// The MessageDialog that pops up when this exception //
// gets thrown is: //
// //
// Internal Server Error (HTTP 500) //
////////////////////////////////////////////////////////
catch (MobileServiceInvalidOperationException e)
{
MessageDialog errormsg = new MessageDialog(e.Message,
string.Format("{0} (HTTP {1})",
e.Response.ReasonPhrase,
(int)e.Response.StatusCode));
var ignoreAsyncOpResult = errormsg.ShowAsync();
}
}
private void DivisionButtonSave_Click(object sender, RoutedEventArgs e)
{
var DivisionItem = new Division
{
DivisionTitle = DivisionInput.Text
};
InsertDivision(DivisionItem);
}
I also added a script in the management portal:
function insert(item, user, request) {
if (item.DivisionTitle.length > 15) {
request.respond(statusCodes.BAD_REQUEST, 'Division title must be under 15 characters');
}
else {
request.execute();
}
}
Before making the changes above, I was having no trouble communicating with Azure from within the app and wasn't having any problems inserting data. It's only after editing the script in Azure (the default insert method is simply the request.execute() statement), and since I added the InsertDivision method (I was previously entering data into the db directly from the event handler with the command await App.MobileService.GetTable<Division>().InsertAsync(DivisionItem);) that this problem has started to occur. I've tried a couple of different things and nothing has worked. After looking at my code does anything stick out? Thanks in advance to anyone who can help.
In the request sent to the service, the property DivisionTitle is sent with the first letter in lower case (since you defined it as such with the JsonProperty attribute):
{"divisionTitle":"the actual title"}
On your script, you're trying to access the property item.DivisionTitle (which doesn't exist, JavaScript is case-sensitive), and then access a property (length) of this undefined value. That will cause an error in your script. If you either change the script to use the actual JSON name (item.divisionTitle.length > 15) or change the JsonProperty declaration in the client to send the property with the first letter in upper case, it should work.
By the way, if you go to the "logs" tab in the portal, you should see some error which explains why you're getting the internal server error.

asp.net web api internal server error 500

On my local server it returns json but when I publish it to remote server. I get 500 internal server error.
I can't run the hosted site in debug mode, on local host it seems fine. The remote server is IIS8.5 X-AspNet-Version: 4.0.30319 and my local IIS is iis 8
The html response is
500 - Internal server error.
There is a problem with the resource you are looking for, and it cannot be displayed.
Api Controller
public class loginapiController : ApiController
{
[System.Web.Http.HttpGet]
public UserProjects UserProjects(int id)
{
return new UserProjects(id);
}
}
public class UserProjects
{
public IEnumerable<Project> Projects = new List<Project>();
public List<Project> ExistingUserProjects = new List<Project>();
public int UserId { get; set; }
public UserProjects(int userId)
{
UserId = userId;
var context = new AuditConnection();
var existingProjectIds = context.UserProjects.Where(up => up.UserId == userId).Select(p => p.ProjectId);
foreach (var id in existingProjectIds)
{
var project = context.Projects.Where(p => p.ProjectId == id).First();
ExistingUserProjects.Add(project);
}
var proj = context.Projects.ToList();
Projects = proj.Except(ExistingUserProjects);
}
}
You can look at the exception using this link http://sentry.landsea.com.au/api/loginapi/UserProjects/4
EDIT: given the error
"There is already an open DataReader associated with this Command which must be closed first."
I believe this answer will explain your issue. Your best bet is to let your Reader finish the first command before starting another (you could do this with ToList()), or collapse the query down to a single call (I believe that you could do this by refactoring your queries into one which uses SelectMany()).
Keep in mind that turning on MARS may be covering up a bigger problem, and you should understand it fully before you go that route, especially for a web application with the lifetime scope of your DbContext should be setup as the life of a single http request. Doing that is trivial if you're properly manually disposing of your context or if you're using a dependency injection framework.
For me the URL shows a clear error message: "Message":"There is already an open DataReader associated with this Command which must be closed first." You can only have one DataReader open at once, so the code has to be fixed.

Categories