I am looking for a good way to perform logs change/audit trail on EF5 database first.
The main problem i'm having is that currently an old application is ruining and it creates logs using Triggers, but on that application the database connection uses a specific user for each user on the application (every user on the application has his own database user), so when they do a log they use a lot of the connection properties as default values like userID, and Host, also many logged tables doesn't have an userID row so if i use EF, the entity i want to update/insert/delete doesn't have any user data.
but my application (MVC4) has only 1 string connection using only 1 user (same database user for each) so the triggers will store the userId of the database user from the connection string.
so what will be a good way to create logs using EF? is there a way to do it using triggers?(and passing userID and others?).
i have being reading about override onUpdate functions but also they say it wont work on EF5
In the DatabaseContext it is possible to override the SaveChanges function.
You can test the changeset for entries that needs to be logged.
Maybe it's to low-level i.e. to close to the datalayer, but it will work in EF.
You'll get something like this:
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.State == EntityState.Added)
{
var needToLogAdd = entry.Entity as INeedToLogAdd;
if (needToLogAdd != null)
DoLogAdd(needToLogAdd);
}
}
base.SaveChanges();
}
Related
Background
I have a central database my MVC EF web app interacts with following best practices. Here is the offending code:
// GET: HomePage
public ActionResult Index()
{
using (var db = new MyDbContext())
{
return View(new CustomViewModel()
{
ListOfStuff = db.TableOfStuff
.Where(x => x.Approved)
.OrderBy(x => x.Title)
.ToList()
});
}
}
I also modify the data in this database's table manually completely outside the web app.
I am not keeping an instance of the DbContext around any longer than is necessary to get the data I need. A new one is constructed per-request.
Problem
The problem I am having is if I delete a row or modify any data from this table manually outside the web app, the data being served by the above code does not reflect these changes.
The only way to get these manual edits of the data to be picked up by the above code is to either restart the web app, or use the web app to make a modification to the database that calls SaveChanges.
Log Results
After logging the query being executed and doing some manual tests there is nothing wrong with the query being generated that would make it return bad data.
However, in logging I saw a confusing line in the query completion times. The first query on app start-up:
-- Completed in 86 ms with result: CachingReader
Then any subsequent queries had the following completion time:
-- Completed in 0 ms with result: CachingReader
What is this CachingReader and how do I disable this?
Culprit
I discovered the error was introduced elsewhere in my web app as something that replaced the underlying DbProviderServices to provide caching, more specifically I am using MVCForum which uses EF Cache.
This forum's CachingConfiguration uses the default CachingPolicy which caches everything unless otherwise interacted with through the EF which was the exact behavior I was observing. More Info
Solution
I provided my own custom CachingPolicy that does not allow caching on entities where this behavior is undesirable.
public class CustomCachingPolicy : CachingPolicy
{
protected override bool CanBeCached(ReadOnlyCollection<EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
{
foreach (var entitySet in affectedEntitySets)
{
var table = entitySet.Name.ToLower();
if (table.StartsWith("si_") ||
table.StartsWith("dft_") ||
table.StartsWith("tt_"))
return false;
}
return base.CanBeCached(affectedEntitySets, sql, parameters);
}
}
With this in place, the database logging now always shows:
-- Completed in 86 ms with result: SqlDataReader
Thanks everyone!
I have created a page asp.net MVC for inserting records to database where i am giving a preview button to see how the data will looks on client side before saving it. I am using session to pass model to the preview page. On preview page i have created a button which will save the model in session to database but it is throwing exception "An entity object cannot be referenced by multiple instances of IEntityChangeTracker".
I am using the same dbContext. I had tried many solutions given by users but they are not working for me. I have attached the part of the code that's throwing exception. Please see where I am doing wrong.
Here is the code where I am saving record
var model = Session[Constants.SessionVariables.ProjectModelForPreview] as Project;
if (create != null)
{
if (model.Id == 0)
{
if (model.IsFeatured)
{
foreach (var item in dbContext.Projects.Where(p => p.IsFeatured == true))
{
item.IsFeatured = false;
}
dbContext.SaveChanges();
}
dbContext.Entry(model).State = EntityState.Unchanged;
dbContext.SaveChanges();
TempData["SuccessMessage"] = "Project created successfully.";
return RedirectToAction("Index");
}
}
Your controller, and therefore your DbContext, is instantiated per request. So your application follows this flow:
Request 1 instantiates DbContext 1.
You load an entity through DbContext 1. This entity is being tracked, and the entity itself holds a reference to DbContext 1.
You store this entity in the session, keeping the old DbContext alive. This works, because the default session state works InProc, and not by serialization.
Request 2 comes in, DbContext 2 gets instantiated.
The entity is retrieved from the session.
You try to save the entity, still being tracked by DbContext 1, through DbContext 2. This throws.
Now for the solution there are various approaches:
Don't save entities in the session at all. Persist them, and look them up again in successive requests.
Save entities in the session by manually serializing them.
Load the entity with .AsNoTracking().
Let's ignore the original problem for now,it will be solved once you refactor the code
1)if for some reason you are using the same context across request ,stop.
(I don't think you do though).
2)Don't save tracked entities in the Session*
Search on google to see how EF tracks changes.
3)Read 1 and 2 again
*Use .AsNoTracking() or project your entity in a new model and save that in the session
I have an MVC5, which uses ASP.NET Identity for users. I have a class named Business which inherits from ApplicationUser, then I populate the database with the entries in my CSV files, but then in the database they don't have a SecurityStamp and I cannot seem to be able to log in. I tried something like this in my Configuration.cs file, but it doesn't seem to work:
var userManager = new UserManager<Business>(new UserStore<Business>(context));
foreach (Business b in context.Businesses)
{
userManager.UpdateSecurityStampAsync(b.Id);
}
context.SaveChanges();
Please note that initially their SecurityStamp is null in the database. Any idea, how to add the security stamps from Configuration.cs?
You should always use the non-async versions of methods that are not intended to be awaited.
userManager.UpdateSecurityStamp(b.Id);
Change context.Businesses to context.Businesses.ToList()
The error you were getting There is already an open DataReader associated with this Connection which must be closed first. is probably because you are iterating a set which is streaming objects from your DB and at the same time trying to issue additional commands through UpdateSecurityStamp
In my web application, when a user registers, I have to store his credentials in a table, his profile in another table.
public long CreateUser(User user) {
using(EF db = new EF()) {
db.AddToUsers(user);
db.SaveChanges();
return user.UserId;
}
}
public void CreateUserAuth(UserAuth userAuth) {
using(EF db = new EF()) {
db.AddToUserAuths(userAuth);
db.SaveChanges();
}
}
ex: When User1 registers, I save user1, city, country in Users table.
And save user1, password, CreatedOn in UserAuths table.
In this case, My code is
CreateUser(user);
CreateUserAuth(userAuth);
...
If CreateUserAuth fails for some reason, How can I revert back the query in CreateUser?
Is there a way to achieve this without making lot of structural changes?
I created an entire website using similar functions. Now I realize that if one function fails, there is no way to revert the already executed statements.
Thanks in advance.
Well you can accomplish this by using transaction. This article might help you in understanding transaction
http://msdn.microsoft.com/en-us/library/vstudio/bb738523(v=vs.100).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
I make a Mvc4 application from defaults I've been given at the beginning.
I need to store the modified UserProfile in my DB, so I have changed the UserProfile class for fulfilling my needs and also changed the RegisterModel class. Now when I register someone, I have a correct view with all the necessary fields for it, however, when I open Server Explorer for check, I get a UserProfile table with UserId and UserName only(but by the model there should also be firstname,lastname,email and so on).
What should be modified more for storing them correctly?
I was in the same spot a few months ago. One of the issues is that the registration story isn't exactly completed out of the box. For example, if someone register first THEN link their external account, they can wind up with multiple profiles.
I pull the user id from the webpages_OAuthMembership table on successful login.
if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: true))
{
var oAuthMembership = new EndeavorODataEntities().webpages_OAuthMembership
.Where(u => u.ProviderUserId == result.ProviderUserId)
.FirstOrDefault(x => x.Provider == result.Provider) ??
new webpages_OAuthMembership
{
Provider = result.Provider,
ProviderUserId = result.ProviderUserId,
};
TempData.Add("OAuthMembership", oAuthMembership);
HttpContext.Response.Cookies.Add(new HttpCookie("UserId", oAuthMembership.UserId.ToString(CultureInfo.InvariantCulture)));
return RedirectToAction("Summary", new { Controller = "Member", id = oAuthMembership.UserId.ToString(CultureInfo.InvariantCulture) });
From here, I make a separate call to my custom 'Membership' table on another controller, as I store all of my application data in a separate db from the this OAuthMember table. In my prior experience in using the ASP.NET Membership provider database, I always kept that as a separate db from my applications, reusing it across multiple apps. Of course, if you wished to modify the UserProfile or other tables, as you can see from the code above, this is just a LINQ statement. There is nothing to say you couldn't perform a join to the UserProfiles table here too.
In this example above I had created an edmx file OAuthMembership.edmx and imported my tables from SQL just like any other database. EndeavorODataEntities is the name of my connection string, and webpages_OAuthMembership is the name of the actual membership table.
I've added in some other resources that I've used which may help you.
http://blogs.msdn.com/b/webdev/archive/2012/08/22/extra-information-from-oauth-openid-provider.aspx
http://blogs.msdn.com/b/pranav_rastogi/archive/2012/08/23/plugging-custom-oauth-openid-providers.aspx
http://blogs.msdn.com/b/webdev/archive/2012/08/24/customizing-the-login-ui-when-using-oauth-openid.aspx
http://blogs.msdn.com/b/webdev/archive/2012/09/12/integrate-openauth-openid-with-your-existing-asp-net-application-using-universal-providers.aspx
http://blogs.msdn.com/b/webdev/archive/2012/09/19/configuring-your-asp-net-application-for-microsoft-oauth-account.aspx
http://blogs.msdn.com/b/rickandy/archive/2012/08/15/initializesimplemembership-attribute-and-simplemembership-exceptions.aspx