I building my first MVC project and having problem with handling concurrency.
Web application was working fine (adding, editing and deleting) before I decided to add concurrency handling.
//Entity class Line
public class Line
{
[Key]
[Required(ErrorMessage = "Please enter a line name")]
public string Name { get; set; }
[Required(ErrorMessage = "Please enter a Business Unit name")]
public string BU { get; set; }
[Required(ErrorMessage = "Please enter a Department name")]
public string Department { get; set; }
[Required(ErrorMessage = "Please enter a location of the line")]
public string Location { get; set; }
[Required(ErrorMessage = "Please enter a target output")]
[Range(0.01, double.MaxValue, ErrorMessage = "Please enter a positive target output")]
public int TargetOutput { get; set; }
[Required(ErrorMessage = "Please enter a target yiel")]
[Range(0.01, 1.00, ErrorMessage = "Please enter a positive number in range of 0.01 to 1.00")]
public Double TargetYield { get; set; }
[Timestamp]
public Byte[] Timestamp { get; set; }
}
code from Controller:
// Saves changes to the Line
[HttpPost]
public ActionResult Edit(Line line)
{
if (ModelState.IsValid)
{
repository.SaveLine(line);
TempData["message"] = string.Format("{0} has been saved", line.Name);
return RedirectToAction("Index");
}
else
{
// return to lines list if there is something wrong with the data
return View(line);
}
}
code from Entity Framework repository class ( this part of code is not competed yet, at the moment when concurrency occurred it supposed to just replace timestamp with the one from db and save it again) :
// Save changes to the line or create new one if not exists
public void SaveLine(Line line)
{
// Checking if line with the same name already exists
//Line found = Lines.FirstOrDefault(l => l.Name == line.Name);
string found = Lines
.Select(l => l.Name)
.Where( n => n == line.Name)
.SingleOrDefault();
if (found == null)
{
context.Lines.Add(line);
}
else
{
context.Entry(line).State = System.Data.EntityState.Modified;
}
//context.SaveChanges();
try
{
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var dbvalues = (Line)entry.GetDatabaseValues().ToObject();
line.Timestamp = dbvalues.Timestamp;
context.SaveChanges();
}
}
When I run application in only one tab and trying to edit line entity I'm getting this error:
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
I have debug and check the timestamp value, it is null. But in database is 0x0000000000000814. When in next step timestamp is replaced with the one from database and trying to save again it throws the same exception.
I have not idea where I have made mistake, any help is much appreciated
Excuse me for my English.
Thank you
You need to write your timestamp out to the view to ensure it comes back into your entity.
Html.HiddenFor(o=>o.Timestamp)
It works when I run single instance of application, but when I run two of them for testing concurrency. I get exception which I have handled and replaced timestamp with the one from the db, but same exception raised again even when both of the timestamps are the same:
From immediate window:
?line.Timestamp
{byte[8]}
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 70
[7]: 81
?dbValues.Timestamp
{byte[8]}
[0]: 0
[1]: 0
[2]: 0
[3]: 0
[4]: 0
[5]: 0
[6]: 70
[7]: 81
Why is that? Should it not only raise exception when timestamps are different?
Resolved: It looks like when I try to save again in code it actually still holding old object with old timestamp. I have moved try catch statement to controller and changed to replace timestamp and return Edit View when concurrency occured. Now when user press Save again exception is not throwed and data is saved.
Related
I have a problem In association with validation property! I use .net core 2.1. I checked unique fields in OnModelCreating method in DbContext class and it's working fine. now I want to display an error message if the user enters the same BirthCertificate value (already exists in the database) in the input field like that Display & Required & MaxLength & ... attributes and send (bind) it to ModelState to check it. i also use jquery.validate.js in the client and show all errors and it's working fine. how I should do this:
Public Class Person
{
[Display(Name = "Enter BirthCertificate")]
[Required(ErrorMessage = "Please enter {0}")]
[MaxLength(10, ErrorMessage = "Max lenght is {0}")]
public string BirthCertificate { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<DomainClasses.Person.Person>(entity =>
{
entity.HasIndex(e => e.BirthCertificate).IsUnique(); // it's working fine
});
}
thanks
You can use either one of these.
1) Create new unique key for BirthCertificate in SQL Server. Use catch (Exception ex) and return to ajax to show error.
2) Do checking code
if (db.Person.Where(x => x.BirthCertificate.Contains(birthcert)).Any())
{
//return to ajax to show error
}
Update:
This is now driving me crazy!
After much Googling etc. I am really no closer to a solution.....
However I have found one thing that is puzzling me even more - the "States" of the entities just before the m_dbContext.SaveChanges() call. (see below for full repository code)
var updateInfoState = m_dc.Entry(oldPage.UpdateInfo).State; // State is 'Modified'
var oldPageState = m_dc.Entry(oldPage).State; // State is 'Detached'
this.m_dc.SaveChanges();
Why is "oldPage" detached?
Getting quite desperate now!! ;)
Original:
I appear to be having a problem with EF Code-First updating related tables correctly.
In this simplified example, the 'UpdateInfo' table IS being updated OK with the new DateTime .... but the 'Pages' table is not being updated with the new 'Name' value.
I am seeding code-first POCOs via DropCreateDatabaseAlways / override Seed ... and EF is creating the test tables correctly - so at this point it seems to know what it is doing....
I am sure this is something simple/obvious I am missing!
All help very much appreciated!
My Class definitions:
public class Page
{
public int Id { get; set; }
public string Name { get; set; }
public virtual UpdateInfo UpdateInfo { get; set; } // virtual For Lazy loading
}
[Table("UpdateInfo")] // much better than EF's choice of UpdateInfoes!
public class UpdateInfo
{
public int Id { get; set; }
public DateTime DateUpdated { get; set; }
}
public class DomainContext : DbContext
{
public DbSet<Page> Pages { get; set; }
public DbSet<UpdateInfo> UpdateInfo { get; set; }
}
Tables created by Code-First
Pages Table
===========
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](max) NULL,
[UpdateInfo_Id] [int] NULL,
UpdateInfo Table
================
[Id] [int] IDENTITY(1,1) NOT NULL,
[DateUpdated] [datetime] NOT NULL,
My Repository code:
public Page Get(int id)
{
Page page = m_dbContext.Pages.Single(p => p.Id == id);
return page;
}
public void Update(PagePostModel model)
{
Page oldPage = Get(model.PageModel.Id); // on oldPage Name = "Hello", DateUpdated = "Last Year"
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel); // on newPage Name = "Goodbye" (AutoMapper)
newPage.UpdateInfo = oldPage.UpdateInfo; // take a copy of the old UpdateInfo since its not contained in the model
newPage.UpdateInfo.DateUpdated = DateTime.UtcNow; // update to now
oldPage = newPage; // copy the updated page we grabbed from dbContext above (NB Everything looks great here..oldPage is as expected)
m_dbContext.SaveChanges(); // update - only the 'UpdateInfo' table is being updated - No change to 'Pages' table :(((
}
As you know, there is a change tracker api in Entity Framework.
To track the changes of your entities you retrieved from the database, DbContext uses its reference value.
Your "Update" function above inserts newPage into oldPage. So, DbContext never knows oldPage is a newPage. So, it is "detached".
However, for UpdateInfo, it is copy of reference in oldPage, so DbContext can track change of that. So, it is "modified".
To solve this problem, how about using the code below?
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel);
oldPage.UpdateInfo = newPage.UpdateInfo;
oldPage.UpdateInfo.DateUpdated = DateTime.UtcNow;
m_dbContext.SaveChanges();
Update
Then, use Attach & Detach methods.
Those methods help you attach and detach entities from DbContext.
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel);
// if you attach first, there will be an exception,
// because there is two entities having same id.
m_dbContext.Entry(oldPage).State = EntityState.Detached;
m_dbContext.Pages.Attach(newPage);
// if you don't set IsModified = true,
// DbContext cannot know it is changed.
m_dbContext.Entry(newPage).State = EntityState.Modified;
m_dbContext.SaveChanges();
I am following Scott Gu's blog: here
In his Blog he talks about client and server side validation.
How does one validate if username has already been taken and display this as a validation error message to the user?
In Scott's blog, this would be the same as validating if Title is unique:
public class Dinner
{
public int DinnerID { get; set; }
[Required(ErrorMessage = "Please enter a Dinner Title")]
[StringLength(20, ErrorMessage = "Title is too long")]
public string Title { get; set; }
[Required(ErrorMessage = "Please enter the Date of the Dinner")]
public DateTime EventDate { get; set; }
[Required(ErrorMessage = "Please enter the location of the Dinner")]
[StringLength(30, ErrorMessage = "Address is too long")]
public string Address { get; set; }
[Required(ErrorMessage = "Please enter your email address")]
[RegularExpression(".+\\#.+\\..+", ErrorMessage = "Please enter a valid email address")]
public string HostedBy { get; set; }
public virtual ICollection<RSVP> RSVPs { get; set; }
}
My first guess is that somehow this is done within the Model Controller, here:
//
// POST: /Home/Create
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
And because the Title is stored in a database server, this would be server side validation.
I know how to check if the Title is unique, but I do not know how to make the validation message appear in the View like it does using declaratives like [Required] or [StringLength()]. For example, here is how I can check for uniqueness:
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
foreach (var existingDinner in nerdDinners.Dinners)
{
if(existingDinner.Title == dinner.Title)
{
**// TODO: display validation error message?**
}
}
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
Using my imagination, and a magic wand, I would want to create a new declarative called [TitleIsUnique] that performs like the other validation declaratives.
Thank you in advance for your assistance.
You could create a custom attribute as mentioned and use IValidateObject but I prefer to add my errors to the ModelState in one of the layers in my application.
For this you can use ModelState.AddModelError
If you use ModelState.AddModelError("Title", "Title must be unique"); it will add an error to the Title field.
If you use ModelState.AddModelError("*", "Title must be unique"); it will add a general error message for the page.
[HttpPost]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
if(nerdDinners.Dinners.Any(d => d.Title == dinner.Title))
{
ModelState.AddModelError("Title", "The title is not unique");
return View(dinner);
}
nerdDinners.Dinners.Add(dinner);
nerdDinners.SaveChanges();
return RedirectToAction("Index");
}
return View(dinner);
}
You are probably looking at implementing your own attribute derived from CustomAttribute. Take a look at this blog post http://blogs.msdn.com/b/adonet/archive/2011/05/27/ef-4-1-validation.aspx - it shows how to validate uniqueness. In the post IValidatableObject interface is used to perform validation but you should be able to the same by creating CustomAttribute.
I get an exception
The specified table does not exist [Limits]
while I'm trying saving new item
(App.Current as App).context.Limits.InsertOnSubmit(new Limit() { Date = DateTime.Now, Value = inputLimit });//this works
(App.Current as App).context.SubmitChanges();//here I get exception
Also I get an error on this line:
var currentLimit = (App.Current as App).context.Limits.Where(l => l.Date.Date == DateTime.Now.Date).FirstOrDefault();
Here is a "model"
public class CalCounterContext:DataContext
{
public CalCounterContext(string connstring):base(connstring)
{
}
public Table<Limit> Limits;
public Table<Meal> Meals;
}
[Table]
public class Limit
{
[Column(IsPrimaryKey = true, IsDbGenerated = true, DbType = "Int NOT NULL IDENTITY", CanBeNull = false, AutoSync = AutoSync.OnInsert)]
public int Id { get; set; }
[Column]
public DateTime Date { get; set; }
[Column]
public int Value { get; set; }
}
Sometimes it works, sometimes, doesn't. What could be a reson of my problem?
This normally happens when you add a table in a later version of the database then what is being used. When you create your database context, check to see if it is up to date, and if not, update the database using the DatabaseSchemaUpdater class as described here. If it is just while you are creating the app, uninstall and re-install the app.
Also, I ran into a strange issue where intermittently I would get this error even once the app was in production without any reasoning. Often is occured when I would launch the app and then hit the home or back button to end it quickly. I ended up re-implementing the GetTable<> function used to instantiate my ITable variable in a base database class so that it would do a hard check to see if the table actually existed:
public Table<TEntity> VerifyTable<TEntity>() where TEntity : class
{
var table = GetTable<TEntity>();
try
{
// can call any function against the table to verify it exists
table.Any();
}
catch (DbException exception)
{
if (exception.Message.StartsWith("The specified table does not exist."))
{
var databaseSchemaUpdater = this.CreateDatabaseSchemaUpdater();
databaseSchemaUpdater.AddTable<TEntity>();
databaseSchemaUpdater.Execute();
}
else
{
throw;
}
}
return table;
}
I had the same intermittent error you had. Try removing the database from the device and installing the app again. I found my issue was being caused because I was making changes to the model and when I re-ran the app, I would get this error.
The basic problem...
I have a method which executes the following code:
IList<Gig> gigs = GetGigs().WithArtist(artistId).ToList();
The GetGigs() method gets Gigs from my database via LinqToSql...
So, when GetGigs().WithArtist(artistId).ToList() is executed I get the following exception:
Member access 'ListenTo.Shared.DO.Artist Artist' of 'ListenTo.Shared.DO.Act' not legal on type 'System.Collections.Generic.List`1[ListenTo.Shared.DO.Act]
Note that the extension function "WithArtist" looks like this:
public static IQueryable<Gig> WithArtist(this IQueryable<Gig> qry, Guid artistId)
{
return from gig in qry
where gig.Acts.Any(act => (null != act.Artist) && (act.Artist.ID == artistId))
orderby gig.StartDate
select gig;
}
If I replace the GetGigs() method with a method that constructs a collection of gigs in code (rather than from the DB via LinqToSQL) I do NOT get the exception.
So I'm fairly sure the problem is with my LinqToSQl code rather than the object structure.
However, I have NO IDEA why the LinqToSQl version isnt working, so I've included all the associated code below. Any help would be VERY gratefully receivced!!
The LinqToSQL code....
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
return from g in DBContext.Gigs
let acts = GetActs(g.ID)
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs()
{
return from a in DBContext.Acts
join artist in DBContext.Artists on a.ArtistID equals artist.ID into art
from artist in art.DefaultIfEmpty()
select new ListenTo.Shared.DO.Act
{
ID = a.ID,
Name = a.Name,
Artist = artist == null ? null : new Shared.DO.Artist
{
ID = artist.ID,
Name = artist.Name
},
GigId = a.GigID
};
}
IQueryable<ListenTo.Shared.DO.Act> GetActs(Guid gigId)
{
return GetActs().WithGigID(gigId);
}
I have included the code for the Act, Artist and Gig objects below:
public class Gig : BaseDO
{
#region Accessors
public Venue Venue
{
get;
set;
}
public System.Nullable<DateTime> EndDate
{
get;
set;
}
public DateTime StartDate
{
get;
set;
}
public string Name
{
get;
set;
}
public string Description
{
get;
set;
}
public string TicketPrice
{
get;
set;
}
/// <summary>
/// The Act object does not exist outside the context of the Gig, therefore,
/// the full act object is loaded here.
/// </summary>
public IList<Act> Acts
{
get;
set;
}
#endregion
}
public class Act : BaseDO
{
public Guid GigId { get; set; }
public string Name { get; set; }
public Artist Artist { get; set; }
}
public class Artist : BaseDO
{
public string Name { get; set; }
public string Profile { get; set; }
public DateTime Formed { get; set; }
public Style Style { get; set; }
public Town Town { get; set; }
public string OfficalWebsiteURL { get; set; }
public string ProfileAddress { get; set; }
public string Email { get; set; }
public ImageMetaData ProfileImage { get; set; }
}
public class BaseDO: IDO
{
#region Properties
private Guid _id;
#endregion
#region IDO Members
public Guid ID
{
get
{
return this._id;
}
set
{
this._id = value;
}
}
}
}
I think the problem is the 'let' statement in GetGigs. Using 'let' means that you define a part of the final query separately from the main set to fetch. the problem is that 'let', if it's not a scalar, results in a nested query. Nested queries are not really Linq to sql's strongest point as they're executed deferred as well. In your query, you place the results of the nested query into the projection of the main set to return which is then further appended with linq operators.
When THAT happens, the nested query is buried deeper into the query which will be executed, and this leads to a situation where the nested query isn't in the outer projection of the query to execute and thus has to be merged into the SQL query ran onto the DB. This is not doable, as it's a nested query in a projection nested inside the main sql query and SQL doesn't have a concept like 'nested query in a projection', as you can't fetch a set of elements inside a projection in SQL, only scalars.
I had the same issue and what seemed to do the trick for me was separating out an inline static method call that returned IQueryable<> so that I stored this deferred query into a variable and referenced that.
I think this is a bug in Linq to SQL but at least there is a reasonable workaround. I haven't tested this out yet but my assumption is that this problem may arise only when referencing static methods of a different class within a query expression regardless of whether the return type of that function is IQueryable<>. So maybe it's the class that holds the method that is at the root of the problem. Like I said, I haven't been able to confirm this but it may be worth investigating.
UPDATE: Just in case the solution isn't clear I wanted to point it out in context of the example from the original post.
public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
{
var acts = GetActs(g.ID); // Don't worry this call is deferred
return from g in DBContext.Gigs
join venue in DBContext.Venues on g.VenueID equals venue.ID
select new ListenTo.Shared.DO.Gig
{
ID = g.ID,
Name = g.Name,
Acts = new List<ListenTo.Shared.DO.Act>(acts),
Description = g.Description,
StartDate = g.Date,
EndDate = g.EndDate,
IsDeleted = g.IsDeleted,
Created = g.Created,
TicketPrice = g.TicketPrice,
Venue = new ListenTo.Shared.DO.Venue {
ID = venue.ID,
Name = venue.Name,
Address = venue.Address,
Telephone = venue.Telephone,
URL = venue.Website
}
};
}
Note that while this should correct the issue at hand there also seems to be another issue in that the deferred acts query is being accessed in each element of the projection which I would guess would cause separate queries to be issued to the database per row in the outer projection.
I don't see anything in your classes to indicate how LINQ to SQL is meant to work out which column is which, etc.
Were you expecting the WithArtist method to be executed in .NET, or converted into SQL? If you expect it to be converted into SQL, you'll need to decorate your Gig class with appropriate LINQ to SQL attributes (or configure your data context some other way). If you want it to be executed in code, just change the first parameter type from IQueryable<Gig> to IEnumerable<Gig>.
I found out that an issue like this (which I also had recently) can be resolved, if you convert the IQueryable (or Table) variable Gigs into a list like so
return from g in DBContext.Gigs.ToList()
...
If that still doesn't work, do the same for all the IQueryables. The reason behind seems to me that some queries are too complex to be translated into SQL. But if you "materialize" it into a list, you can do every kind of query.
Be careful, you should add "filters" (where conditions) early because too much memory consumption can become a problem.