I've configured my Database First EF6 DbContext to log the queries it generates but I'm noticing that the queries it provides are parameterized. For debugging purposes I wanted to output the values of each of the parameters. I wrote an interceptor class and configured it to output the parameters like in the below code snippet, but it still doesn't output the parameter value. What am I doing wrong? What's the correct way to output the parameter values for the queries that Entity Framework generates. I know that in EF Core there's a setting on the OptionsBuilder to enable logging of sensitive data but I can't find any similar setting in EF6.
public class LoggingInterceptor : DatabaseLogFormatter {
public LoggingInterceptor(DbContext context, Action<string> writeAction) : base(context,writeAction) {
}
public override void LogCommand<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext)
{
var sql = new StringBuilder();
sql.Append(command.CommandText);
sql.Append("\n");
foreach (var param in command.Parameters) {
sql.Append(param.ToString());
sql.Append("\n");
}
Write($"Entity Framework SQL : {sql}");
}
public override void LogResult<TResult>(DbCommand command, DbCommandInterceptionContext<TResult> interceptionContext){}
}
You don't need to do anything special (like your inerceptor), if you add this code before your query to the database you will get the query and the parameters on the output window of Visual studio:
context.Database.Log = x => System.Diagnostics.Debug.WriteLine(x);
Related
I'm fairly new to C# programming but am competent in SQL. I'm trying to write my first application (a data monitoring system) which saves SQL statements and thresholds from user input then on a squeal, runs the statements and checks to see if they have breached their respective threshold, this then alerts the user on screen and also sends an email/text if applicable.
Here I have some code that grabs data from 2 tables (this is ASP.NET Core MVC so they are models):
Widget - Which is where the check information is kept (we only care about the statement and connection string id.
ConnectionStrings - Which is where each connection string is stored.
I'm trying to get it to pull back a list of the checks, then their corresponding connection string, pass them into SqlConnection or a Dapper method and log the results to a table where they can be passed back to the user.
The bit I'm stuck at is that GetConnectionStringId and Query, both of which throw the error mentioned in the title. Obviously I'm trying to do a foreach in the list, using .FistOrDefault is not the answer I'm looking for here unless anyone can properly explain why it should be. It turns my IEnumerable error into a bool error and should be needless to say none of these values are booleans.
Is there a simple way around this or am I going about this the completely wrong way?
Don't be shy with a response I'm quite happy to be told I've got no clue what I'm doing, thanks in advance!
Also if more information is needed please let me know!
using Dapper;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Data;
using SystemBoard.Data;
namespace SystemBoard.Controllers
{
public class WidgetCheckController : Controller
{
private readonly ApplicationDbContext _context;
public WidgetCheckController(ApplicationDbContext context)
{
_context = context;
}
// * Testing with list idea
public async Task<List<IActionResult>> GetCheckResult()
{
var checks = await _context.Widget
.Where(x => x.DateDeleted == null)
.ToListAsync();
foreach (var WidgetId in checks)
{
var GetConnectionStringId = checks.Select(x => x.ConStringId);
var ActualConnectionString = _context.ConnectionStrings.First(x => x.ConnectionStringId = GetConnectionStringId);
// I don't know if it's better to write it like this? I get the same error under 'checks.Select(x => x.ConStringId)'
// var ActualConnectionString = _context.ConnectionStrings.First(x => x.ConnectionStringId = checks.Select(x => x.ConStringId));
var Query = checks.Select(x => x.SQLStatement);
using (IDbConnection connection = ActualConnectionString)
{
connection.Query(Query);
}
}
}
}
}
The Add method does not insert a new record into the database.
Working with the database through the application.
The SelectAll request is executed.
The Insert request is not executed. There are no errors.
The Insert request code. Not running(Not working)
public void Insert(Source source)
{
using (var DbContext = new DbContextSQlite())
{
dbContext.Sources.Add(source);
dbContext.SaveChanges();
}
}
The selectAll request code.
Public void selectAll() is running
{
using (var DbContext = new DbContextSQlite())
{
var rows = from x in dbContext.Sources
select x;
int count = rows.Count();
}
}
Working with the database via DB Browser using SQL queries
The Select query is executed.
The Insert request is being executed.
Used.
DB Browser for SQLite Version 3.12.2;
Visual Studio Community 2019. 16.11.10;
Application-Console. .NET Framework 4.7.2;
Picture-1
Picture-2
Update-1
Update-1.
When I try to fully post a question, stackoverflow.com gives me comments on the design. I don't understand how to eliminate these comments.
That's why I'm posting the question in an online editor.
Follow the link -> Detailed question.
We're using DBUP to handle db migrations. Each release, we would like to run the dbup console app with a command line switch so that during dev we can re-run our scripts while we're working on them, however we don't want it to re-run all the previous releases scripts which already appear in the database. How can this be achieved?
We added a '-debug' command line switch to our DbUp console application. If this is present we switch which Journal class is used when talking to the database.
The Journal class (https://dbup.readthedocs.io/en/latest/more-info/journaling/) in DbUp is the class that interacts with the database to check and record which scripts have already been run (stored by default in the Schema Versions table). For Dev, we force this to use a read-only version of this, which can check which scripts are already present (to prevent you re-running everything each time) but prevents new records being recorded, so that next time it will attempt to re-run your new scripts again.
The read only journal looks like this;
public class ReadOnlyJournal : IJournal
{
private readonly IJournal _innerJournal;
public ReadOnlyJournal(IJournal innerJournal)
{
_innerJournal = innerJournal;
}
public void EnsureTableExistsAndIsLatestVersion(Func<IDbCommand> dbCommandFactory)
{
_innerJournal.EnsureTableExistsAndIsLatestVersion(dbCommandFactory);
}
public string[] GetExecutedScripts()
{
return _innerJournal.GetExecutedScripts().ToArray();
}
public void StoreExecutedScript(SqlScript script, Func<IDbCommand> dbCommandFactory)
{
// don't store anything
}
}
Then an extension method to allow the use of this new journal to be easier specified;
public static class DbUpHelper
{
public static UpgradeEngineBuilder WithReadOnlyJournal(this UpgradeEngineBuilder builder, string schema, string table)
{
builder.Configure(c => c.Journal = new ReadOnlyJournal(new SqlTableJournal(() => c.ConnectionManager, () => c.Log, schema, table)));
return builder;
}
}
And then finally the change to your DbUp console app;
var upgrader = debug
? DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.WithReadOnlyJournal("dbo", "SchemaVersions")
.LogToConsole()
.Build()
: DeployChanges.To
.SqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.LogToConsole()
.Build();
var result = upgrader.PerformUpgrade();
if (!result.Successful)
....
Per the FAQ (1), I can add additional databases to my existing connection in a number of ways. I have tried them all, and none work for SQL Azure.
In fact, SQL Azure as a provider, doesn't even include the option to "Include additional databases."
Can someone please tell me a workaround for LinqPad to connect two databases? I am trying to create a migration linqpad script to sync data from one database to another.
http://www.linqpad.net/FAQ.aspx#cross-database
This fails because SQL Azure does not let you create linked servers. See
Can linked server providers be installed on a SQL Azure Database instance?
If you simply want to copy data from one database to another, and the schemas are the same, a workaround is to create a separate connection using the same TypedDataContext class:
void Main()
{
CopyFrom<Customer>("<source connection string>");
}
void CopyFrom<TTable> (string sourceCxString) where TTable : class
{
// Create another typed data context for the source. Note that it must have compatible schema:
using (var sourceContext = new TypedDataContext (sourceCxString) { ObjectTrackingEnabled = false })
{
// Delete the rows currently in our table:
ExecuteCommand ("delete " + Mapping.GetTable (typeof (TTable)).TableName);
// Insert the rows from the source table into the target table and submit changes:
GetTable<TTable>().InsertAllOnSubmit (sourceContext.GetTable<TTable>());
SubmitChanges();
}
}
Simple Select Example:
void Main()
{
SimpleSelect("<your conn string>");
}
void SimpleSelect (string sourceCxString)
{
// Create another typed data context for the source. Note that it must have compatible schema:
using (var sourceContext = new TypedDataContext (sourceCxString) { ObjectTrackingEnabled = false })
{
sourceContext.Assignee.OrderByDescending(a => a.CreateTimeStamp).Take(10).Dump();
Assignee.OrderByDescending(a => a.CreateTimeStamp).Take(10).Dump();
}
}
I am building in a Change History / Audit Log to my MVC app which is using the Entity Framework.
So specifically in the edit method public ActionResult Edit(ViewModel vm), we find the object we are trying to update, and then use TryUpdateModel(object) to transpose the values from the form on to the object that we are trying to update.
I want to log a change when any field of that object changes. So basically what I need is a copy of the object before it is edited and then compare it after the TryUpdateModel(object) has done its work. i.e.
[HttpPost]
public ActionResult Edit(ViewModel vm)
{
//Need to take the copy here
var object = EntityFramework.Object.Single(x=>x.ID = vm.ID);
if (ModelState.IsValid)
{
//Form the un edited view model
var uneditedVM = BuildViewModel(vm.ID); //this line seems to confuse the EntityFramework (BuildViewModel() is used to build the model when originally displaying the form)
//Compare with old view model
WriteChanges(uneditedVM, vm);
...
TryUpdateModel(object);
}
...
}
But the problem is when the code retrieves the "unedited vm", this is causing some unexpected changes in the EntityFramework - so that TryUpdateModel(object); throws an UpdateException.
So the question is - in this situation - how do I create a copy of the object outside of EntityFramework to compare for change/audit history, so that it does not affect or change the
EntityFramework at all
edit: Do not want to use triggers. Need to log the username who did it.
edit1: Using EFv4, not too sure how to go about overriding SaveChanges() but it may be an option
This route seems to be going nowhere, for such a simple requirement! I finally got it to override properly, but now I get an exception with that code:
public partial class Entities
{
public override int SaveChanges(SaveOptions options)
{
DetectChanges();
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry).GetModifiedProperties(); //This line throws exception The ObjectStateManager does not contain an ObjectStateEntry with a reference to an object of type 'System.Data.Objects.EntityEntry'.
var currentValues = ObjectStateManager.GetObjectStateEntry(entry).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
//return base.SaveChanges();
return base.SaveChanges(options);
}
}
IF you are using EF 4 you can subscribe to the SavingChanges event.
Since Entities is a partial class you can add additional functionality in a separate file. So create a new file named Entities and there implement the partial method OnContextCreated to hook up the event
public partial class Entities
{
partial void OnContextCreated()
{
SavingChanges += OnSavingChanges;
}
void OnSavingChanges(object sender, EventArgs e)
{
var modifiedEntities = ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in modifiedEntities)
{
var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
foreach (var propName in modifiedProps)
{
var newValue = currentValues[propName];
//log changes
}
}
}
}
If you are using EF 4.1 you can go through this article to extract changes
See FrameLog, an Entity Framework logging library that I wrote for this purpose. It is open-source, including for commercial use.
I know that you would rather just see a code snippet showing how to do this, but to properly handle all the cases for logging, including relationship changes and many-to-many changes, the code gets quite large. Hopefully the library will be a good fit for your needs, but if not you can freely adapt the code.
FrameLog can log changes to all scalar and navigation properties, and also allows you to specify a subset that you are interested in logging.
There is an article with high rating here at the codeproject: Implementing Audit Trail using Entity Framework . It seems to do what you want. I have started to use this solution in a project. I first wrote triggers in T-SQL in the database but it was too hard to maintain them with changes in the object model happening all the time.