ASP.NET MVC 4, EF5, Code First, SQL Server 2012 Express
What is best practice to enforce a unique value in a model? I have a places class that has a 'url' property that should be unique for every place.
public class Place
{
[ScaffoldColumn(false)]
public virtual int PlaceID { get; set; }
[DisplayName("Date Added")]
public virtual DateTime DateAdded { get; set; }
[Required(ErrorMessage = "Place Name is required")]
[StringLength(100)]
public virtual string Name { get; set; }
public virtual string URL { get; set; }
};
Why isn't there just a [Unique] data annotation you can place on it?
I have seen 1 or 2 discussions on this, but no talk of best practice. Using Code First can you somehow tell the database to set a unique constraint on the field in the database?
What is easiest way - and what is best practice?
As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use FluentValidation. Then the code will be very easy to read and super-maintainable since validation will be managed on separate class meaning less spaghetti code.
Pseudo-example of what you are trying to achieve.
[Validator(typeof(PlaceValidator))]
class Place
{
public int Id { get; set; }
public DateTime DateAdded { get; set; }
public string Name { get; set; }
public string Url { get; set; }
}
public class PlaceValidator : AbstractValidator<Place>
{
public PlaceValidator()
{
RuleFor(x => x.Name).NotEmpty().WithMessage("Place Name is required").Length(0, 100);
RuleFor(x => x.Url).Must(BeUniqueUrl).WithMessage("Url already exists");
}
private bool BeUniqueUrl(string url)
{
return new DataContext().Places.FirstOrDefault(x => x.Url == url) == null
}
}
This link might help:
https://github.com/fatihBulbul/UniqueAttribute
[Table("TestModels")]
public class TestModel
{
[Key]
public int Id { get; set; }
[Display(Name = "Some", Description = "desc")]
[Unique(ErrorMessage = "This already exist !!")]
public string SomeThing { get; set; }
}
The only way is to update your migration once you generate it, assuming you are using them, so that it enforces a unique constraint on the column.
public override void Up() {
// create table
CreateTable("dbo.MyTable", ...;
Sql("ALTER TABLE MyTable ADD CONSTRAINT U_MyUniqueColumn UNIQUE(MyUniqueColumn)");
}
public override void Down() {
Sql("ALTER TABLE MyTable DROP CONSTRAINT U_MyUniqueColumn");
}
The hard bit, though, is enforcing the constraint at the code level before you get to the database. For that you might need a repository that contains the complete list of unique values and makes sure that new entities don't violate that through a factory method.
// Repository for illustration only
public class Repo {
SortedList<string, Entity1> uniqueKey1 = ...; // assuming a unique string column
public Entity1 NewEntity1(string keyValue) {
if (uniqueKey1.ContainsKey(keyValue) throw new ArgumentException ... ;
return new Entity1 { MyUniqueKeyValue = keyValue };
}
}
References:
Repository - Fowler (the original source of Repository)
Repostory - MSDN
Tutorial: Repository in MVC (www.asp.net)
Singleton in C# - SO
Footnote:
There are a lot of requests for [Unique] in code first, but it looks like it isn't even making version 6: http://entityframework.codeplex.com/wikipage?title=Roadmap
You could try voting for it here: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support
You may do this checking in the code level before saving the data to the Database tables.
You can try using the Remote data annotation on your viewmodel to do an asynchronous validation to make the UI more responsive.
public class CreatePlaceVM
{
[Required]
public string PlaceName { set;get;}
[Required]
[Remote("IsExist", "Place", ErrorMessage = "URL exist!")
public virtual string URL { get; set; }
}
Make sure you have an IsExists action method in your Placecontroller which accepts a URL paramtere and check it againist your table and return true or false.
This msdn link has a sample program to show how to implement Remote attribute to do instant validation.
Also, If you are using a Stored procedure (For some reason), you can do an EXISTS check there before the INSERT query.
I solved the general problem of enabling constructor injection in your Validation flow, integrating into the normal DataAnnotations mechanism without resorting to frameworks in this answer, enabling one to write:
class MyModel
{
...
[Required, StringLength(42)]
[ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")]
public string MyProperty { get; set; }
....
}
public class MyDiDependentValidator : Validator<MyModel>
{
readonly IUnitOfWork _iLoveWrappingStuff;
public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff)
{
_iLoveWrappingStuff = iLoveWrappingStuff;
}
protected override bool IsValid(MyModel instance, object value)
{
var attempted = (string)value;
return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted);
}
}
With some helper classes (look over there), you wire it up e.g. in ASP.NET MVC like so in the Global.asax :-
DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
typeof(ValidatorServiceAttribute),
(metadata, context, attribute) =>
new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));
Faced similar issue in my ASP.NET Razor Page Project. Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.
I needed unique Book Name. This is how I resolved:
I added unique constraint to the field in database via EF Core migrations. Added following in ApplicationDbContext class and then ran migration.
Code:
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Book>()
.HasIndex(u => u.Name)
.IsUnique();
}
Next, created helper/extension method as follows.
Code:
// Validate uniqueness of Name field in database.
// If validation is done on existing record, pass the id of the record.
// Else, if validating new record Name, then id is set to dummy key integer -1
public static bool UniqueNameInDb(this string data, ApplicationDbContext db, int id = -1)
{
var duplicateData = from o in db.Book
where o.Name == data && o.Id != id
select o;
if(duplicateData.Any())
{
return false;
}
return true;
}
}
Then used it in Create and Edit page model in OnPost() method as follows.
Create model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
if (!Book.Name.UniqueNameInDb(_db)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
await _db.Book.AddAsync(Book);
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
else
{
return Page();
}
}
Edit Model:
public async Task<IActionResult> OnPost()
{
if(ModelState.IsValid)
{
var bookFromDb = await _db.Book.FindAsync(Book.Id);
if (!Book.Name.UniqueNameInDb(_db, Book.Id)) //<--Uniqueness validation
{
ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
return Page();
}
bookFromDb.Name = Book.Name;
bookFromDb.Author = Book.Author;
await _db.SaveChangesAsync();
return RedirectToPage("Index");
}
return Page();
}
PS: Your Razor view should've Model validation set on in the form to capture and display the error.
i.e,
<div class="text-danger" asp-validation-summary="ModelOnly"></div>
and below validation against the field.
<span asp-validation-for="Book.Name" class="text-danger"></span>
well it's simple but idk if this is efficient or not. Just check before adding a new user whether the email already exists or not.
if (!db.Users.Any(x => x.Email == data.Email))
// your code for adding
else
// send a viewbag to the view
// ViewBag.error = "Email Already Exist";
To achieve Unique for multiple Columns
modelBuilder.Entity<DataClass>()
.HasIndex(u => new
{
u.col1,
u.col2
})
.IsUnique();
Related
This should be really simple but I think I'm having possible issues with my model. I have been working with linq over a year and I should have this simple remove easily done. Please help! It's removing both records from the database when I only want one deleted
I have a database table with these properties.
Email, EmployeeName, StoreId
jsch#m.com,Joe Schneider,9
jsch#m.com,Joe Schneider,8
I need to delete Joe Schneider with storeId 9
So I run this simple query and remove process.
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Permissions.Remove(PersonToRemove);
db.SaveChanges();
}
I am assuming one is going to say, hey your model is not right and don't put the name as a key, but I can't just be changing the model because other parts of the app are based on this model and would cause huge breaks. Could you give me advise how to edit the linq query to not delete both records?
model
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
public string Department { get; set; }
public int? StoreId { get; set; }
public String Email { get; set; }
}
[Table("Permissions")]
public class Permissions
{
[Key]
public String EmployeeName { get; set; }
}
The problem is here you are defining a primary key which has no length constraint on it. (MaxLength). This leads to EF generate a column with NVARCHAR(MAX). As mentioned here VARCHAR(MAX) columns are not allowed to be primary key. So correct definition should be like below
[Table("Permissions")]
public class Permissions
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)] <--
[MaxLength(255)] // <---
public String EmployeeName { get; set; }
}
Edit: You need to recreate the database in order to associated tables initialized with correct settings.
Edit 2 : Also you may need a DatabaseGenerated(DatabaseGeneratedOption.None) since its not identity column.
you can set Deleted state on individual entity like so:
var temp2 = difference[i];
var PersonToRemove = db.Permissions.SingleOrDefault(s => s.EmployeeName == temp2 && s.StoreId == Persons.StoreId);
if (PersonToRemove.EmployeeName != null)
{
db.Entry(PersonToRemove).State = EntityState.Deleted; // do this instead
db.SaveChanges();
}
EF should then figure out which entity you wanted to delete
UPD
I am assuming you are using EF6 and DB-first approach. I am also assuming you've got your DB context class set up with default convention model builder. It seems EF's default object tracking based on Key will not work as your key is not unique (this is a bigger problem, but I understand you're already aware of that).
You might try circumvent that convention by adding custom model builder configuration like so:
class MyDbContext : DbContext {
public virtual DbSet<Permissions> Permissions {get;set;}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Permissions>().HasKey(p => new { p.EmployeeName, p.StoreId});
}
}
since you didn't share your DbContext definition this is just a snippet but hopefully gives you some ideas to explore.
this is the API reference: https://learn.microsoft.com/en-us/dotnet/api/system.data.entity.modelconfiguration.entitytypeconfiguration-1?view=entity-framework-6.2.0
I am using a generic repository and Entity Framework. I can update one of the classes normally, but I'm having trouble updating the relationship between them.
I'm also using lazy loading, AutoMapper and a service layer to isolate the domain.
public class DetalhesDoArquivoViewModel
{
public DetalhesDoArquivoViewModel()
{
Id = Guid.NewGuid();
}
[Key]
public Guid Id { get; set; }
public string FileName { get; set; }
public string Extension { get; set; }
public Guid FormularioId { get; set; }
public virtual FormularioDoUploadViewModel DescricaoDoUpload { get; set; }
}
public class FormularioDoUploadViewModel
{
public FormularioDoUploadViewModel()
{
Id = Guid.NewGuid();
}
[Key]
public Guid Id { get; set; }
[Required(ErrorMessage = "Digite um nome")]
[Display(Name = "Nome")]
[MaxLength(100)]
public string Nome { get; set; }
[Required(ErrorMessage = "Entre com uma descrição")]
[Display(Name = "Descrição")]
[MaxLength(500)]
public string Descricao { get; set; }
public virtual IEnumerable<DetalhesDoArquivoViewModel> DetalhesDoArquivo { get; set; }
}
My Update repository
public virtual TEntity Atualizar(TEntity obj)
{
var entry = Db.Entry(obj);
Dbset.Attach(obj);
entry.State = EntityState.Modified;
SaveChanges();
return obj;
}
My service class:
public class UploadAppServices : BaseService, IUploadServices
{
private readonly IFormularioUploadRepository _formularioUploadRepository;
private readonly IDetalhesDoArquivoRepository _detalhesDoArquivoRepository;
// Update
public FormularioDoUploadViewModel Atualizar(FormularioDoUploadViewModel formularioDoUploadViewModel)
{
var form = Mapper.Map<FormularioUpload>(formularioDoUploadViewModel);
_formularioUploadRepository.Atualizar(form);
Commit();
return formularioDoUploadViewModel;
}
//getById
public FormularioDoUploadViewModel ObterPorId(Guid id)
{
return Mapper.Map<FormularioDoUploadViewModel>(_formularioUploadRepository.ObterPorId(id));
}
}
My controller:
public class FormularioDoUploadController : BaseController
{
private ApplicationDbContext db = new ApplicationDbContext();
private IFormularioUploadRepository _formularioUploadRepository;
private IUploadServices _uploadServices;
public ActionResult Edit(Guid id)
{
var formularioDoUploadViewModel = _uploadServices.ObterPorId(id);
if (formularioDoUploadViewModel == null)
{
return HttpNotFound();
}
return View(formularioDoUploadViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(FormularioDoUploadViewModel formularioDoUploadViewModel)
{
if (ModelState.IsValid)
{
for (int i = 0; i < Request.Files.Count; i++)
{
var file = Request.Files[i];
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
DetalhesDoArquivoViewModel detalhesDoArquivo = new DetalhesDoArquivoViewModel()
{
FileName = fileName,
Extension = Path.GetExtension(fileName),
FormularioId = formularioDoUploadViewModel.Id,
};
var path = Path.Combine(Server.MapPath("~/App_Data/Upload/"), detalhesDoArquivo.Id + detalhesDoArquivo.Extension);
file.SaveAs(path);
}
// Update
_uploadServices.Atualizar(formularioDoUploadViewModel);
return RedirectToAction("Index");
}
}
return View(formularioDoUploadViewModel);
}
Automapper is great for mapping entity to view-model, but I would avoid using it to map from a view-model to entity. This may seem convenient, but you are effectively unconditionally trusting the data received from the client and overwriting your database data. This means you have to send 100% of your entity domain model to the client, revealing more about your domain structure than you need to, and then accept that expanded domain model which can contain alterations that your client application does not intend to make. (intercepting the post to the server in the browser debugger and altering values in the object posted back to the server)
Submit actions should be coded to:
Validate that the current session user has permission to modify the record(s) identified by the submit request.
Limit the update to specific values provided in the request.
Validate those specific values.
Disconnect the user session and notify administrators if any of the above is violated.
In some cases, such as adding a new entity, the payload will effectively be a complete entity and potentially some related details. This still needs to be validated against the known data state. In other cases where you provide an action that updates an entity, the model posted back should merely contain the ID of the entity being updated, and the specific values the client is allowed to update. (not the entire, modified entity)
By passing entities, or view models that map directly to entities for a method intended to update some aspects of the entity, I can:
Re-assign that entity to someone else.
Use the request to attempt to assign another random entity to myself.
Negate or otherwise change any and all data recorded in that entity.
Do not trust anything received from the client.
This issue also presents a concurrent access issue where your system is adopting a "last in wins" scenario. Between the time you provided the entity/view model and the time you submit the view model back to the server, that entity data may have changed. By mapping the data into a new entity class, attaching, marking modified, and saving, you overwrite the data without any consideration as to whether the data was stale.
To avoid the issue you are seeing, and the security/stale issues, you should load the entity from the context on the Update post call, validate the authorization for the current user, check the row version # or timestamp to ensure the record isn't stale, validate your updated details, then, once you're absolutely sure that the data in your view model presents no risk to your entity, you can use automapper's .Map(source, detination) to copy the values across. If you need to update related entities against related view models, then as long as you .Include() those related entities when you retrieve the entity from the context, then the .Map() call should handle the related data.
I am new to stackoverflow! Please forgive me if I do something wrong.
I have now searched around hours for a solution, unsuccessfully...
Background:
EF Model "Role":
public class IdItem {
[Key]
public Int32 ID { get; set; }
}
public class NamedItem : IdItem {
public String Name { get; set; }
}
public class Right : NamedItem {
...
}
public class Role : NamedItem {
/* [1] */ public ICollection<Int32> RightIDs { get; set; } // <-- Key-Collection of Nav-Prop
/* [2] */ public virtual ICollection<Right> Rights { get; set; } // <-- Nav-Prop
...
}
View "Roles.cshtml" containing:
var sliRights = /* All available Rights as SelectListItem { Value = Right.ID } */
var rightsAtts = new { #class = "form-control selectpicker", multiple = true };
#Html.ListBoxFor(x => x.RightIDs, sliRights, rightsAtts)
This way, the ID's of all selected Rights are correctly stored in the "RightIDs" property of class "Role".
Q: However, how can I tell EF to map the Keys stored in "RightIDs" to synchronously (vice-versa) use it with the "Rights"-Property (see [2])?
I guess something like [ForeignKey("RightIDs")] as att. for [2] (tried, not working)?
Update
For example something like:
public class Role : NamedItem {
public ICollection<Int32> RightIDs { get; set; }
[ForeignKey("RightIDs")] // <-- normally for non-collections, but tried -> not working at all
public virtual ICollection<Right> Rights { get; set; }
...
}
Currently, I am using an interface for class "Role" implementing a method to read all IDs from "Rights" into "RightIDs" and another method to perform the way back, both using DbContext as parameter, invoked by responsible controller:
public interface IScalarEntity {
void ToScalarProperties(DbContext context);
void FromScalarProperties(DbContext context);
}
Ugly when using this current approach for at least 20 upcoming entity classes...
Edit
As I now have researched, there is no sense to get EF to use a ICollection where T is a primitive type, even if T is a type equal to an key property of another entity Type.
Now going to use the workaround for this kind of problem.
Thanks to all contributors!
What you are doing looks really weird. First of all, you are mixing Database logic with ViewModel logic, which is a bad practice. Do you really need the IDs for the collection of Rights, when you can just get them from the Right object?
You can remove the RightIds property from your Role class and use a simple select statement in the controller:
var rights = role.Rights.ToList().Select(r => new SelectListItem
{
Value = r.ID.ToString(),
Text = r.Name
});
return View(rights);
Now in the view you will have a model with SelectListItems, which you can use with many of the List controls available in Razor.
I'm using the CQRS approach in my architecture, as an example, I have a command like:
public class ModifyDepartmentInformationCommand
{
public ModifyDepartmentInformationCommand() { }
public ModifyDepartmentInformationCommand(int departmentId, string departmentName, byte[] version)
{
DepartmentId = departmentId;
DepartmentName = departmentName;
Version = version;
}
public int DepartmentId { get; set; }
public string DepartmentName { get; set; }
public byte[] Version { get; set; }
}
Its handler looks like:
public class ModifyDepartmentInformationCommandHandler :
IRequestHandler<ModifyDepartmentInformationCommand, ModifyDepartmentInformationCommandResult>
{
private readonly IMgpCommandContext _mgpCommandContext;
public ModifyDepartmentInformationCommandHandler(IMgpCommandContext mgpCommandContext)
{
_mgpCommandContext = mgpCommandContext;
}
public ModifyDepartmentInformationCommandResult Execute(ModifyDepartmentInformationCommand request)
{
new ModifyDepartmentInformationCommandValidator().ValidateAndThrow(request);
var department = _mgpCommandContext.Departments.SingleOrDefault(o => o.Id == request.DepartmentId);
if (department == null) { throw new ApplicationException("DepartmentDoesNotExist", "There is no such department."); }
department.ModifyInformation(request.Version, request.DepartmentName);
_mgpCommandContext.SaveChanges();
return new ModifyDepartmentInformationCommandResult();
}
}
As you see Version is used as the concurrency token. It is passed with the command, and in the command handler passed to the department's domain entity operation ModifyInformation, which sets the version:
public class Department
{
...
public void ModifyInformation(byte[] version, string departmentName)
{
Version = version;
Name = departmentName;
new DepartmentValidator().ValidateAndThrow(this);
}
}
Because the Version property on the Department domain entity is mapped using EF Code First as follows:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>().ToTable("Department").Property(p => p.Version).IsRowVersion().IsConcurrencyToken(); ;
}
...concurrency is detected correctly from the moment SaveChanges is executed on the context.
That's how it works now... the question I have is related to the fact that I have one concurrency token on department level. So, if I have commands that each modify different properties of a department, the same concurrency token is used.
As a result, if two users query department X, and user 1 uses a command to change the department name and user 2 uses another command to change some other property of department; then the second user that submits the command may still get a concurrency violation even if it's not the same property that was modified.
Is this the right approach, or is there a better way to handle concurrency in commands, like using multiple concurrency tokens for an entity? What approach is generally taken?
You choose right approach. Also, you can split the department configuration into several logical parts, for example: common parameters, security, etc. Each part would be considered in isolation and each part will have its own version.
My domain class:
public class Address
{
[Key]
public virtual string AddressId { get; set; }
public virtual string Address { get; set; }
}
In my MVC controller I want to check the given Address exist, before I insert.
public ActionResult Create(Address address)
{
if (ModelState.IsValid)
{
if (db.Addresses.Any(a => a.AddressId == address.AddressId)) // how I do it now
{
ModelState.AddModelError(string.Empty, "Address Id already exists!");
}
else
{
db.Addresses.Add(address);
db.SaveChanges();
return RedirectToAction("Index");
}
}
}
But there are lot of other domain classes in my project and I want to do the same check again and again.
My question is I want to write a generic method in my Db context class to perform this check. (looks like below or similar)
public bool Exists(object) {
// return true if exist
}
i.e. a method which I can call like this:
db.Exists(address)
Thanks!
You could use generics and do something like the following:
public class YourDbContext : DbContext
{
...
public bool Exists<TEntity>(object id)
where TEntity : class
{
var dbSet = Set<TEntity>();
var entity = dbSet.Find(id);
return entity != null;
}
Which you'd then use like:
db.Exists<Address>(address.AddressId);
Using Find isn't the most efficient way to handle this, but it has the key benefit that you're not required to know what the actual primary key property on the class is, which would greatly complicate this method. For example, Address has AddressId, but Foo might have FooId.
UPDATE
Since ultimately this just uses Find under the hood, you just have to modify the method slightly to be able to take multiple parameters. Find handles composite keys by allowing one more parameters to be passed to it. But bear in mind, the the order matters and must align with the key order you specified when configuring your entity.
public bool Exists<TEntity>(params object[] keys)
where TEntity : class
{
var dbSet = Set<TEntity>();
var entity = dbSet.Find(keys);
return entity != null;
}