In my MVC application I have a requirement where I want user to insert Unique value in a column.
i.e.: Username should be unique in Users table.
I used [Indes(IsUnique = true)] data annotation in my model.
But when I insert duplicate value in the field it throws an exception, but I want to display an Error Message on my View saying Please try with a different Username
Please help me what should I do here?
You can use one of those:
Write your CustomValidator (ny recommendation)
[CustomRemoteValidator(ErrorMessage = #"Username already in use")]
public string Username{ get; set; }`
And override IsValid method
public override bool IsValid(object value)
{
return !(this.DbContext.Set<User>().Any(a =>
a.Username.Equals((string)value));
}
Check it in your business layer.
Check it before save entity in database by overriding SaveChanges() method.
Related
I have a model which has an auto-incrementing ID field by default as is normal. However, I wish to seed the database with initial data and because there are foreign keys I wish to explicitly set the IDs of the seeded data.
My model
public class EntAttribute
{
public int ID { get; set; }
public string Title { get; set; }
}
My seeding code:
public class Seeder
{
private class AllAttributes
{
public List<EntAttribute> Attributes { get; set; }
}
public bool SeedData()
{
AllAttributes seedAttributes;
string strSource;
JsonSerializer JsonSer = new JsonSerializer();
strSource = System.IO.File.ReadAllText(#"Data/SeedData/Attributes.json");
seedAttributes = JsonConvert.DeserializeObject<AllAttributes>(strSource);
_context.AddRange(seedAttributes.Attributes);
_context.SaveChanges();
return true;
}
}
Please note, I'm very new to both EFCore and C#. The above is what I've managed to cobble together and it seems to work right up until I save the changes. At this point I get:
SqlException: Cannot insert explicit value for identity column in table 'Attribute' when IDENTITY_INSERT is set to OFF.
Now I'm smart enough to know that this is because I can't explicitly set the ID field in the EntAttribute table because it wants to assign its own via auto-increment. But I'm not smart enough to know what to do about it.
Any help appreciated.
EDIT: Adding the solution based on the accepted answer below because the actual code might help others...
So I added to my Context class the following:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasSequence<int>("EntAttributeNumbering")
.StartsAt(10);
modelBuilder.Entity<EntAttribute>()
.Property(i => i.ID)
.HasDefaultValueSql("NEXT VALUE FOR EntAttributeNumbering");
}
This first ensures the a sequence is created (the name is arbitrary) and then secondly, sets it to be used for the relevant table instead of auto-increment. Once this was done I was able to my seed data. There are fewer than 10 records so I only needed to set the start value for the sequence to 10. More would normally make sense but I know there will never be more.
I also had to blitz my migrations because they'd somehow got in a mess but that's probably unrelated.
With EF Core you can create and use a Sequence object to assign the IDs, and you can reserve a range of IDs for manual assignment by picking where the sequence starts. With a Sequence you can assign the IDs yourself, or let the database do it for you.
FYI for people using EF Core 3, if using int for your key you can set the start sequence value incase you have seeded data. I found this a much cleaner to solve this problem in my use case which just had a single seeded record.
e.g
modelBuilder.Entity<TableA>()
.Property(p => p.TableAId)
.HasIdentityOptions(startValue: 2);
modelBuilder.Entity<TableA>()
.HasData(
new TableA
{
TableAId = 1,
Data = "something"
});
https://github.com/npgsql/efcore.pg/issues/367#issuecomment-602111259
I have a code first model with a mapping table so that I can map MenuItem to an IdentityRole, enabling the production of a menu based on the logged in users role assignment.
public class MenuItem
{
public int Id { get; set; }
public string Text { get; set; }
}
public class MenuRoleMap
{
[Key]
public int Id { get; set; }
public virtual MenuItem MenuItem { get; set; }
public virtual IdentityRole Role { get; set; }
}
The IdentityRole and rest of Identity is auto wired via IdentityDbContext which I've inherited through my ApplicationDbContext like this, then the context should be consistent.
public class ApplicationDbContext : IdentityDbContext<User>
All of the tables look right, they have the expected columns and foreign keys, here is the MenuRoleMap table
I have an valid existing instance of MenuItem and IdentityRole which I use to try and add a new entity item to this table
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges(); /// <<<=== HERE ERROR BECAUSE THE role IS ALREADY IN DB
Which throws this error
A first chance exception of type 'System.Data.Entity.Validation.DbEntityValidationException' occurred in EntityFramework.dll
Role: Role SystemsAdministrator already exists.
Which of course, it does exist, I know that, it's already in the database. Surely the EF should not be trying to add a new entity item for the foreign key entity if it already exists?
It doesn't do it for the MenuItem, only the IdentityRole.
I thought the problem was proxy creation since the IdentityRole was a proxy object, so I turned that off
this.Configuration.ProxyCreationEnabled = false;
but I still get the same error.
My question is, how do you add an entity where the foreign key is an IdentityRole?
Thank you stackers.
ANSWER TO MY OWN QUESTION
After assistance from those below I discovered while investigating the various solutions that the problem wasn't one of context per se but the validity of an object. The object looked right, what I hadn't realised is that it wasn't the object from the context, it was a facsimile. By trying to add this facsimile to the model, the context quite rightly says it already exists, you can't add it again. By trying to override the state of the item I created a different kind of error.
The resolution was simply to reload the object from the context and then add that to the parent item like so
foreach (IdentityRole role in selectedRoles)
{
// Here I'm getting the role from the context using the ID I have from the facsimile
IdentityRole roleToUse = db.Roles.Where(x => x.Id == role.Id).FirstOrDefault();
// carry on as normal
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = roleToUse; // note I'm using the retrieved 'roleToUse'
db.MenuRoleMaps.Add(mrm);
}
db.SaveChanges();
Hey presto it all works.
It looks like you've retrieved the entity from another context and then assigned it to an entity which is then added to a different context. It would then try to insert the Role entity too.
Are you returning the Role from another method where the lifetime of the context is scoped to that method?
You may find the following link useful in regards to updating the state of objects:
Entity states and SaveChanges
The problem is that when you use db.Set<MyEntity>.Add you will mark all entities that are attached to the entity that is added as being added too. You have to explicitly mark them as being unchanged:
foreach (IdentityRole role in selectedRoles)
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
db.MenuRoleMaps.Add(mrm);
db.Entry(role).State=EntityState.Unchanged;
}
db.SaveChanges();
My answer is you can't or at least should not.
Authentication (Roles), and Business (Menu) are different concerns of the application.
For me you have to bring in the ApplicationDb, the part of IdentityDb that you need and organize the synchronisation.
To illustrate my saying: Imaging you use Google or LiveID as authentification provider: can you imagine navigation properties from you ApplicationDd to Google or Microsoft Dbs ?
Clearly not.
So create a AppRole replicating the Role of the authentication database and use this table from your application database to build your menus.
In pseudo code this looks like:
List<Int32> l = IdentityContext.GetRolesForUser(currentUserId);
foreach (AppRole role in AppContext.Roles.Where(r => l.Contains(r.Id)))
{
MenuRoleMap mrm = new MenuRoleMap();
mrm.MenuItem = menuItem;
mrm.Role = role;
appContext.MenuRoleMaps.Add(mrm);
}
appContext.SaveChanges();
Another solution would be to use the same context for Application and Identity.
Inheritance of context seems fine, but I never tested it.
I currently have an object like this (simplified):
public class Image {
public int Id { get; set; }
public int ExternalId { get; set; }
}
Now let's say I have this method (mostly pseudo-code):
public void GetImage(int externalId) {
var existingImage = db.Images.FirstOrDefault(i => i.ExternalId == externalId);
if (existingImage != null) {
return existingImage;
}
var newImage = new Image() { ExternalId = externalId };
db.Images.Attach(newImage);
db.SaveChanges();
return newImage;
}
Because ExternalId isn't a key, the change tracker won't care if I have "duplicate" images in the tracker.
So now, let's say this method gets called twice, at the same time via AJAX and Web API (my current scenario). It's async, so there are two threads calling this method now.
If the time between calls is short enough (in my case it is), two rows will be added to the database with the same external ID because neither existing check will return a row. I've greatly simplified this example, since in my real one, there's a timing issue as I fetch the "image" from a service.
How can I prevent this? I need the image to be returned regardless if it's new or updated. I've added a Unique Constraint in the database, so I get an exception, but then on the client, the call fails whereas it should use the existing image instead of throwing an exception.
If I understand EF correctly, I could handle this by making ExternalId a primary key and then use concurrency to handle this, right? Is there any way to avoid changing my current model or is this the only option?
If you already have property defining uniqueness of your entity (ExternalId) you should use it as a key instead of creating another dummy key which does not specify a real uniqueness of your entity. If you don't use ExternalId as a key you must put unique constraint on that column in the database and handle exception in your code to load existing Image from the database.
I have a Supplier entity, each Supplier object may reference another Supplier object as its 'parent'.
public class Supplier
{
public int? Id { get; set; }
public virtual Supplier Parent { get; set; }
}
This all works as expected until I attempt to remove the relationship, as in, this supplier no longer has a parent. I can change it from null to a particular supplier and I can set it to a different supplier but setting it to null is not persisted after SaveChanges().
supplier.Parent = null;
The foreign key 'ParentId' in the Supplier table is set as nullable. Explicitly defining the relationship doesn't help.
modelBuilder.Entity<Supplier>().HasOptional(s => s.Parent).WithMany();
I'm sure I'm missing something obvious.
Just found another place in my code where I do the exact same thing (that works) and found this;
// Must access property (trigger lazy-loading) before we can set it to null (Entity Framework bug!!!)
var colour = modelItem.Colour;
modelItem.Colour = null;
Did the same in the new code and it all works.
Instead of just modelBuilder.Entity<Supplier>().HasOptional(s => s.Parent).WithMany(); use the following modelBuilder.Entity<Supplier>().HasOptional(s => s.Parent).WithMany().HasForeignKey(x=>x.ParentId);
Otherwise it has no idea what the foreign key's name is
My task is to change the ErrorMessage property of the DataAnnotation validation attribute in MVC2.0. For example I should be able to pass an ID instead of the actual error message for the Model property and use that ID to retrieve some content(error message) from a another service e.g database, and display that error message in the View instead of the ID. In order to do this I need to set the DataAnnotation validation attribute’s ErrorMessage property.
[StringLength(2, ErrorMessage = "EmailContentID.")]
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
It seems like an easy task by just overriding the DataAnnotationsModelValidatorProvider ‘s
protected override IEnumerable GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable attributes)
However it seems to be a complicated enough.
a. MVC DatannotationsModelValidator’s ErrorMessage property is read only. So I cannot set anything here
b. System.ComponentModel.DataAnnotationErrorMessage property(get and set) which is already set in MVC DatannotationsModelValidator so we cannot set again. If you try to set you get “The property cannot set more than once…” error message appears.
public class CustomDataAnnotationProvider : DataAnnotationsModelValidatorProvider
{
protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes)
{
IEnumerable<ModelValidator> validators = base.GetValidators(metadata, context, attributes);
foreach (ValidationAttribute validator in validators.OfType<ValidationAttribute>())
{
messageId = validator.ErrorMessage;
validator.ErrorMessage = "Error string from DB And" + messageId ;
}
//......
}
}
Can anyone please help me on this?
Here is the question: What is your motivation to changing the error message property?
Think this through very carefully, as you are heading down a path where you are obfuscating what is actually happening in the application. Certainly the database informatino is useful, but it is not really part of the validation, nor should it be.
When you head in this direction, you are essentially saying that the validation can only be invalid if there is a database problem. I see two issues with this:
It breaks the separation of concerns. You are reporting a persistance error in the model, which is not where it occurred.
The solution is not unit testable, as you must engage the database.
I don't like either of the two above.
Can you solve this? Possibly if you will create your own custom validation attribute. I would have to check and ensure that is correct. Another option is to aim for custom validation:
http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx
This article can also help you head in the direction you desire:
http://ryanrivest.com/blog/archive/2010/01/15/reusable-validation-error-message-resource-strings-for-dataannotations.aspx
Do you want to solve this? Not really if you are attempting to keep a proper separation of concerns in your application. I would not polute my validation error message (this is not valid) with a database error (I am not valid, but the database also blew up). Just my two cents.
There are built in ways to get the error message via a resource. Instead of a database lookup to get a resource at runtime, generate resources from your database and use that for your error messages.
You can then use the ErrorMessageResourceName and ErrorMessageResourceType to allow the DataAnnotation to perform a resource lookup instead of hard-coding a specific string.
public sealed class MyModel
{
[Required(
ErrorMessageResourceName="MyDescriptionResource",
ErrorMessageResourceType=typeof(MyCustomResource))]
public string Description { get; set; }
}
Also you may want to have a look at ValidationAttribute.FormatErrorMessage Method on msdn.
This method formats an error message
by using the ErrorMessageString
property. This method appends the name
of the data field that triggered the
error to the formatted error message.
You can customize how the error
message is formatted by creating a
derived class that overrides this
method.
A quick sample (and not meant to be a definitive example)
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false,
Inherited = true)]
public sealed class PostCodeValidationAttribute
: ValidationAttribute
{
public override bool IsValid(object value)
{
if( value == null )
return true;
string postCode = value as string;
if( string.IsNullOrEmpty(postCode) )
return true;
if ( !PostCode.IsValidPostCode(postCode, this.PostCodeStyle) )
return false;
return true;
}
public PostCodeStyle PostCodeStyle { get; set; }
public override string FormatErrorMessage(string name)
{
return string.Format(
"{0} is not a valid postcode for {1}", name, PostCodeStyle);
}
}
* I've omitted the PostCodeStyle enumeration as well as the PostCode class for validating a postcode.