I have a setup where I use a service to log a user in and autofill the CreatedBy, UpdatedBy, ... fields for my entities. Because of this my SaveChanges method looks like this:
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added || x.State == EntityState.Modified));
var activeUserId = ActiveUserService.UserId;
var username = Users.First(x => x.Id == activeUserId).UserName;
foreach (var entry in modifiedEntries)
{
IAuditableEntity entity = entry.Entity as IAuditableEntity;
if (String.IsNullOrEmpty(username))
{
throw new AuthenticationException(
"Trying to save entities of type IAuditable while not logged in. Use the IActiveUserService to set a logged in user");
}
if (entity != null)
{
DateTime now = DateTime.Now;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = username;
entity.CreatedAt = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedAt).IsModified = false;
}
entity.UpdatedBy = username;
entity.UpdatedAt = now;
}
}
return base.SaveChanges();
}
The problem is when I'm seeding my database, Autofac isn't running and isn't injecting my services. Is it possible somewhere set a flag of some sort that when I'm seeding my database I use a default username or something?
In your code, if there is no user logged in (so no activeUserId) it will break with an exception on First():
var username = Users.First(x => x.Id == activeUserId).UserName;
And so the line:
if (String.IsNullOrEmpty(username))
will never be true.
I would take a nullable variable into the method: SaveChanges(int? activeUserId = null), and when called by your Seed-method it will be null, and you can handle that to set the username into SYSTEM. But for all your other code you can supply the id of the user, and handle that.
Edit: to summary: the method itself should not check if the user is logged in, do that someplace else.
My current work-around is creating an other SaveChanges-method when seeding the database which sets a default user.
internal int SaveChangesWithDefaultUser()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == EntityState.Added || x.State == EntityState.Modified));
var username = "SYSTEM";
foreach (var entry in modifiedEntries)
{
IAuditableEntity entity = entry.Entity as IAuditableEntity;
if (entity != null)
{
DateTime now = DateTime.Now;
if (entry.State == EntityState.Added)
{
entity.CreatedBy = username;
entity.CreatedAt = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedAt).IsModified = false;
}
entity.UpdatedBy = username;
entity.UpdatedAt = now;
}
}
return base.SaveChanges();
}
Related
I'm trying to unlock a user account from the admin panel:
public async Task<ActionResult> EnableAccount(string id)
{
var user = UserManager.Users.FirstOrDefault(x => x.Id == id);
if(user != null)
{
await UserManager.ResetAccessFailedCountAsync(user.Id);
await UserManager.SetLockoutEndDateAsync(user.Id, null );
}
return RedirectToAction("Users");
}
I'm getting this error:
cannot convert from '<null>' to 'DateTimeOffset' in
await UserManager.SetLockoutEndDateAsync(user.Id, null);
Ended up doing it this way:
var user = UserManager.Users.FirstOrDefault(x => x.Id == id);
user.LockoutEndDateUtc = null;
user.LockoutEnabled = false;
The problem is basically the Save changes method is not working (updating), the method should receive 3 parameters, item id, user id and the object which contains the updated information from the UI, however the code seems to be something bad because the saveChanges() method is not working.
This is my code:
[HttpPost]
[AllowAnonymous]
public JsonResult UpdatePersonalData(int ItemId, int UserId, CND_PersonalData Item)
{
try
{
if (ModelState.IsValid)
{
using (var context = new DexusEntities())
{
CND_PersonalData PersonalData = context.CND_PersonalData.Where(d => d.Id == ItemId && d.UserId == UserId).SingleOrDefault();
if (PersonalData == null)
{
/// Display bad request
/// User does not exist and/or is not activated
List<RootObject> rootObj = new List<RootObject>();
rootObj.Add(new RootObject
{
msg = "User/Item not found in our DB",
code = "error_07"
});
HttpContext.Response.StatusCode = 404;
HttpContext.Response.TrySkipIisCustomErrors = true;
JsonRes.Message = rootObj;
return Json(JsonRes, JsonRequestBehavior.AllowGet);
}
else
{
PersonalData = Item;
context.SaveChanges();
context.ChangeTracker.DetectChanges();
List<RootObject> rootObj = new List<RootObject>();
rootObj.Add(new RootObject
{
msg = "Information stored/updated successfully",
code = "success_05"
});
HttpContext.Response.StatusCode = 200;
JsonRes.Message = rootObj;
return Json(JsonRes, JsonRequestBehavior.AllowGet);
}
}
}
else
{
List<RootObject> rootObj = new List<RootObject>();
JsonRes.Issue = "The model is not correct";
rootObj.Add(new RootObject
{
msg = "Model is not valid",
code = "error_03"
});
HttpContext.Response.StatusCode = 403;
HttpContext.Response.TrySkipIisCustomErrors = true;// Avoid issues in the HTTP methods
JsonRes.Message = rootObj;
return Json(JsonRes, JsonRequestBehavior.AllowGet);
}
}
catch (Exception ex)
{
string err = ex.ToString();
List<RootObject> rootObj = new List<RootObject>();
JsonRes.Issue = err;
rootObj.Add(new RootObject
{
msg = "Conflict with method, see issue description.",
code = "error_08"
});
HttpContext.Response.StatusCode = 400;// Bad request
HttpContext.Response.TrySkipIisCustomErrors = true;
JsonRes.Message = rootObj;
return Json(JsonRes, JsonRequestBehavior.AllowGet);
}
}
What's wrong with my code?
Thanks in advance.
As I can see you are not adding an item into the DbSet and calling SaveChanges after:
When adding an item you should put it into DbSet
context.CND_PersonalData.Add(item);
context.SaveChanges();
when you want to update just call SaveChanges after you update loaded object
var PersonalData= context.CND_PersonalData.Where(d => d.Id == ItemId && d.UserId == UserId).SingleOrDefault();
PersonalData.Name = item.Name;
PersonalData.Title = item.Title;
context.SaveChanges();
You can't just assign passed object to an entity you got from the DB, you need to change properties. If you do it as you did you didn't change values in the loaded object. So when you call SaveChanges nothing is changed. You need to change properties one by one.
If you don't want to do that then you can attach your item into the db by using Attach method on context.
context.Attach(item);
context.SaveChanges();
but you should be careful because if you load and track item with the same id as you are doing before checking if it is null:
CND_PersonalData PersonalData = context.CND_PersonalData.Where(d => d.Id == ItemId && d.UserId == UserId).SingleOrDefault();
if (PersonalData == null)
{
then you will get an error during the save because the context is already tracking item with the same ID, so you can remove that check and just check if it exists:
if (context.CND_PersonalData.Any(d => d.Id == ItemId && d.UserId == UserId))
{
and then execute your code
I am creating a context in a method inside my entity to check something but I am not tracking anything with it but when I try to save in the calling code context it throws an exception.
This is the calling code in the main context where I want to save:
var espToProcess = db.RootDomainEmailSeriesProgresses;
foreach (var esp in espToProcess)
{
bool carryOn = esp.MoveNext();
db.SaveChanges(); //Exception
if (!carryOn) continue;
//--> rest of my code
}
This is the methods inside the RootDomainEmailSeriesProgress class.
public bool MoveNext()
{
if (this.CompletedTargets == null) this.CompletedTargets = new List<EmailAddress>();
if (this.CurrentTarget != null)
{
this.CompletedTargets.Add(this.CurrentTarget);
this.CurrentTarget = null;
}
this.CurrentProgress = "";
if (this.RootDomain.ContactFilter != RootDomain.ContactFilterType.None)
{
this.Status = EmailSeriesStatus.Aborted;
return false;
}
var allTargets = RootDomainEmailManager.SortDomainsEmailsByDesirability(this.RootDomain.ID);
var toDo = allTargets.Except(this.CompletedTargets);
if (toDo.Count() < 1)
{
this.Status = EmailSeriesStatus.Completed;
return false;
}
List<string> targetEmailList = allTargets.Select(e => e.Email).ToList();
List<EmailFilter> emailFilters = this.GetFilters(allTargets);
if (emailFilters.Any(x => x.Filter == EmailFilterType.Unsubscribe || x.Filter == EmailFilterType.Responded || x.Filter == EmailFilterType.ManualContactOnly))
{
this.Status = EmailSeriesStatus.Aborted;
if (this.RootDomain.ContactFilter == 0) this.RootDomain.ContactFilter = RootDomain.ContactFilterType.HasAssociatedEmailFilter;
return false;
}
this.CurrentTarget = toDo.First();
return true;
}
private List<EmailFilter> GetFilters(List<EmailAddress> allTargets)
{
using (var db = new PlaceDBContext())
{
db.Configuration.AutoDetectChangesEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
var targetEmailList = allTargets.Select(e => e.Email).ToList();
return db.EmailFilters.AsNoTracking().Where(x => targetEmailList.Contains(x.Email)).ToList();
}
}
It throws out this exception:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I can't see why esp gets attached to the other context. I only need that context briefly, how do I kill it off so it stops causing me issues?
that because there are difference DbContext instances in foreach loop and GetFilters method
You can retry this code
var espToProcess = db.RootDomainEmailSeriesProgresses;
foreach (var esp in espToProcess)
{
bool carryOn = esp.MoveNext(db);
db.SaveChanges(); //Exception
if (!carryOn) continue;
//--> rest of my code
}
public bool MoveNext(DbContext db)
{
if (this.CompletedTargets == null) this.CompletedTargets = new
List<EmailAddress>();
if (this.CurrentTarget != null)
{
this.CompletedTargets.Add(this.CurrentTarget);
this.CurrentTarget = null;
}
this.CurrentProgress = "";
if (this.RootDomain.ContactFilter != RootDomain.ContactFilterType.None)
{
this.Status = EmailSeriesStatus.Aborted;
return false;
}
var allTargets =
RootDomainEmailManager.SortDomainsEmailsByDesirability(this.RootDomain.ID);
var toDo = allTargets.Except(this.CompletedTargets);
if (toDo.Count() < 1)
{
this.Status = EmailSeriesStatus.Completed;
return false;
}
List<string> targetEmailList = allTargets.Select(e => e.Email).ToList();
List<EmailFilter> emailFilters = this.GetFilters(allTargets, db);
if (emailFilters.Any(x => x.Filter == EmailFilterType.Unsubscribe ||
x.Filter == EmailFilterType.Responded || x.Filter ==
EmailFilterType.ManualContactOnly))
{
this.Status = EmailSeriesStatus.Aborted;
if (this.RootDomain.ContactFilter == 0)
this.RootDomain.ContactFilter =
RootDomain.ContactFilterType.HasAssociatedEmailFilter;
return false;
}
this.CurrentTarget = toDo.First();
return true;
}
private List<EmailFilter> GetFilters(List<EmailAddress> allTargets, DbContext db)
{
db.Configuration.AutoDetectChangesEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
var targetEmailList = allTargets.Select(e => e.Email).ToList();
return db.EmailFilters.AsNoTracking().Where(x =>
targetEmailList.Contains(x.Email)).ToList();
}
I'm building a simple application where a user can edit their profile including adding/deleting a brand image. This seems to be working fine and is updating the database no problem, however when refreshing the page and retrieving the user details via Membership.GetUser() the result includes the old results and not those from the updated database.
Here is my MembershipUser GetUser override:
public override MembershipUser GetUser(string query, bool userIsOnline)
{
if (string.IsNullOrEmpty(query))
return null;
var db = (AccountUser)null;
// ...get data from db
if (query.Contains("#")){
db = _repository.GetByQuery(x => x.Email == query).FirstOrDefault();
}
else
{
string firstName = query;
string lastName = null;
if (query.Contains(" "))
{
string[] names = query.Split(null);
firstName = names[0];
lastName = names[1];
}
// ...get data from db
db = _repository.GetByQuery(x => x.FirstName == firstName && x.LastName == lastName).FirstOrDefault();
}
if (db == null)
return null;
ToMembershipUser user = new ToMembershipUser(
"AccountUserMembershipProvider",
db.FirstName + " " + db.LastName,
db.ID,
db.Email,
"",
"",
true,
false,
TimeStamp.ConvertToDateTime(db.CreatedAt),
DateTime.MinValue,
DateTime.MinValue,
DateTime.MinValue,
DateTime.MinValue);
// Fill additional properties
user.ID = db.ID;
user.Email = db.Email;
user.FirstName = db.FirstName;
user.LastName = db.LastName;
user.Password = db.Password;
user.MediaID = db.MediaID;
user.Media = db.Media;
user.Identity = db.Identity;
user.CreatedAt = db.CreatedAt;
user.UpdatedAt = db.UpdatedAt;
return user;
}
Note I am using a custom MembershipProvider and MembershipUser. Here is where I am calling that method:
public ActionResult Edit()
{
ToMembershipUser toUser = Membership.GetUser(User.Identity.Name, true) as ToMembershipUser;
Now when I do a separate query just under this line of code straight to the database, not invoking MembershipUser, I get the updated result which in turn updates the MembershipUser result?!
It seems it may be caching the results? Anyway around this? I hope this is clear enough. Thanks
Edit:
It appears that when I set a breakpoint just after :
// ...get data from db
db = _repository.GetByQuery(x => x.FirstName == firstName && x.LastName == lastName).FirstOrDefault();
'db' retrieves the outdated results though surely this is talking to the database? If need be I'll update with my repository pattern
I managed to find a workaround though I'm not happy with this solution, so if anyone can improve upon this please post.
I decided to manually update the MembershipUser instance manually each time I update the image. My controller now looks like this:
private static ToMembershipUser MembershipUser { get; set; }
// GET: Dashboard/AccountUsers/Edit
public ActionResult Edit()
{
if(MembershipUser == null)
MembershipUser = Membership.GetUser(User.Identity.Name, true) as ToMembershipUser;
}
[HttpPost]
[ValidateJsonAntiForgeryToken]
public JsonResult UploadMedia(IEnumerable<HttpPostedFileBase> files, int id)
{
var images = new MediaController().Upload(files);
if (images == null)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json("File failed to upload.");
}
AccountUser accountUser = db.AccountUsers.Find(id);
db.Entry(accountUser).State = EntityState.Modified;
accountUser.UpdatedAt = TimeStamp.Now();
accountUser.MediaID = images[0];
db.SaveChanges();
MembershipUser.Media = accountUser.Media;
MembershipUser.MediaID = accountUser.MediaID;
return Json(new { result = images[0] });
}
[HttpPost]
[ValidateJsonAntiForgeryToken]
public JsonResult DeleteMedia(int id)
{
bool delete = new MediaController().Delete(id, 1);
if (!delete)
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
return Json("Error. Could not delete file.");
}
MembershipUser.Media = null;
MembershipUser.MediaID = null;
return Json("Success");
}
I want to query one more property of the "User" entity.
Basically I need to know, is it possible to extend the below statement to include something like this..
user = session.Query<User>().SingleOrDefault(u => u.Username.ToLower() == identity.ToLower()) && (u => u.Email == email);
I know thats not correct but you get the idea, I want to check the user email as well as the username.
This is the current code..
public static bool IsDuplicateIdentity(string identity, string email, Type type)
{
using(ISession session = NHibernateHelper.SessionFactory().OpenSession())
using (ITransaction tx = session.BeginTransaction())
{
User user = null;
// the entity type is checked and then DB is queried to see if an object with that name and email exists
if (type.BaseType == typeof(User))
{
user = session.Query<User>().SingleOrDefault(u => u.Username.ToLower() == identity.ToLower());
}
tx.Commit();
if (user != null)
{
return true;
}
else
{
return false;
}
}
}
Please note this is an ASP.net MVC 3 application.
Can't you just do this?:
user = session.Query<User>()
.SingleOrDefault(u =>
u.Username.ToLower() == identity.ToLower()
&& u.Email == email);
Sure:
user = session.Query<User>()
.SingleOrDefault(u => u.Username.ToLower() == identity.ToLower()
&& u.Email == email);
Yes, you can combine them and you almost got it right. Try it like this:
user = session.Query<User>().SingleOrDefault(u => u.Username.ToLower() == identity.ToLower() && u.Email == email);
You can write the below code
user = session.Query<User>()
.where(u => u.Username.ToLower() == identity.ToLower() && u.Email == email).SingleOrDefault();