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;
}
Related
Given an ASP.NET Core webapp using Entity Framework Core and an SQL database.
An absolute simple action is throwing this exception when trying to update an entity in the database. First noticed by a bug report in production.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(string id, [Bind("Group")] EditViewModel model)
{
if (id != model.Group.Id) return NotFound();
if (!ModelState.IsValid) return View(model);
_context.Update(model.Group);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
Exception is thrown at the line: _context.Update(model.Group);
InvalidOperationException: The instance of entity type 'Group' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
Clearly there is no other instance. I was able to reproduce the exception in my development environment when I stopped the code with a breakpoint on that line and expanded the Results property of the _context.Group object:
It's understandable, that when expanding the Results, it loads the instance needed to be updated and that's why the exception is thrown. But what's about the deployed production environment?
Thanks for the help!
UPDATE1 Group model:
public class Group
{
[Display(Name = "ID")]
public string Id { get; set; }
public virtual Country Country { get; set; }
[Required]
[Display(Name = "Country")]
[ForeignKey("Country")]
public string CountryCode { get; set; }
[Required]
[Display(Name = "Name")]
public string Name { get; set; }
}
UPDATE2 Based on #Mithgroth's answer, I was able to override the function _context.Update() to not need try-catch every time I use it:
public interface IEntity
{
string Id { get; }
}
public override EntityEntry<TEntity> Update<TEntity>(TEntity entity)
{
if (entity == null)
{
throw new System.ArgumentNullException(nameof(entity));
}
try
{
return base.Update(entity);
}
catch (System.InvalidOperationException)
{
var originalEntity = Find(entity.GetType(), ((IEntity)entity).Id);
Entry(originalEntity).CurrentValues.SetValues(entity);
return Entry((TEntity)originalEntity);
}
}
Use the following instead:
var group = _context.Group.First(g => g.Id == model.Group.Id);
_context.Entry(group).CurrentValues.SetValues(model.Group);
await _context.SaveChangesAsync();
The exception can be caused by many different scenarios but the thing is, you are trying to change the state of an object which is already marked differently.
For instance, this would produce the same exception:
var group = new Group() { Id = model.Id, ... };
db.Update(group);
Or you might have detached N-tier children, that's all possible.
This ensures that you are just overwriting an existing entity's values.
Thanks all for the ideas.
I made this overridden function on my context working without Exception, which is imho a slightly better approach. Also, the primary key name is retrieved using the defined model.
Also, it is EntityFramework 3.0 .NET Core
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Linq;
public override EntityEntry<TEntity> Update<TEntity>(TEntity entity) where TEntity : class
{
if (entity == null)
{
throw new System.ArgumentNullException(nameof(entity));
}
var type = entity.GetType();
var et = this.Model.FindEntityType(type);
var key = et.FindPrimaryKey();
var keys = new object[key.Properties.Count];
var x = 0;
foreach(var keyName in key.Properties)
{
var keyProperty = type.GetProperty(keyName.Name, BindingFlags.Public | BindingFlags.Instance);
keys[x++] = keyProperty.GetValue(entity);
}
var originalEntity = Find(type, keys);
if (Entry(originalEntity).State == EntityState.Modified)
{
return base.Update(entity);
}
Entry(originalEntity).CurrentValues.SetValues(entity);
return Entry((TEntity)originalEntity);
}
I am having an issue with performing a .find on an entity with a single PK.
The base class is as follows;
public abstract class Entity : IEntity
{
public int Id { get; set; }
public bool? IsArchived { get; set; }
}
and the class is
public class Manufacturer : Entity
{
public string Name { get; set; }
}
however, when i attempt a HTTPPUT on the Manufacturer i get the following error;
The number of primary key values passed must match number of primary key values defined on the entity.
Parameter name: keyValues
This is thrown when I call the .find method on the set;
public T Find<T>(int id, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.Set<T>().Find(id, includes);
}
Any ideas why this is occurring?
Update
I am running Code first. The database has generated (this is through VS table design) PK_dbo.Manufacturers (Primary Key, Clustered: Id)
I am operating a SOLID architecture, where my controller is as follows;
[HttpPut]
[Route("")]
public Manufacturer Put(Manufacturer m)
{
var response = ManufactureService.UpdateManufacturer(m);
return response.Result;
}
where the ManufacturerService is as follows;
public ServiceResponse<Manufacturer> UpdateManufacturer(Manufacturer obj)
{
Func<Manufacturer> func = delegate
{
using (var context = _contextFactory())
{
var manufacturer = context.Find<Manufacturer>(obj.Id);
manufacturer.Name = obj.Name;
manufacturer.IsArchived = manufacturer.IsArchived;
context.Update(manufacturer);
return manufacturer;
}
};
return this.Execute(func);
}
where obj is the passed Manufacturer to be updated. and the id is an integer.
to give a little more info, I have a contect which then uses the following;
public T Find<T>(int id, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.Set<T>().Find(id, includes);
}
You pass two key values to Find but EF would only deduce that Id is a PK. Do you really want a nullable bool as part of your PK? I'm not sure if this is allowed. If you do want the nullable bool as part of your key then I think you will need to let EF know about it.
If its failing on Manufacturer, and the only column is Name, then it makes zero sense that your call stack is blaming Find(int id). There is no integer primary key on this table. Maybe that is your problem.
UPDATE
Please try copying the code in the Entity class into Manufacturer. Then remove the subclass relationship. Then see if your put still fails.
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();
I have sets of entities all of them are derived from abstract class
public abstract class NamedEntity : INamedEntity
{
#region Public Properties
public string Description { get; set; }
public string Id { get; set; }
public string Name { get; set; }
#endregion
}
When I persist all entities I want to use Name field as a key, so I override DocumentKeyGenerator and provide such implementation:
store.Conventions.DocumentKeyGenerator = entity =>
{
var namedEntity = entity as NamedEntity;
if (namedEntity != null)
{
return string.Format("{0}/{1}", store.Conventions.GetTypeTagName(entity.GetType()), namedEntity.Name);
}
return string.Format("{0}/", store.Conventions.GetTypeTagName(entity.GetType()));
};
It works fine when I persist the list of entities for the first time, but if I want to persist them again I get an exception
PUT attempted on document 'xxxxx' using a non current etag
I just started using RavenDB, so I cannot understand what I am doing wrong?
Just a guess, but it's probably not with your key generation, but how you are storing them.
On first usage you probably have something like:
var myEntity = new MyEntity(...);
session.Store(myEntity);
...
session.SaveChanges();
That part is fine, but on subsequent usage, you should not be doing the same thing. Instead, it should be more like this:
var myEntity = session.Load<MyEntity>("myentities/foobar");
myEntity.Something = 123;
...
session.SaveChanges();
Note there is no call to .Store() when making changes. This is because the entity is "tracked" by the session, and all changes to it are automatically persisted when you call .SaveChanges()
I am hoping to create a base Entity Class that includes a validation rule that checks if a field called "Title" is unique (which of course requires a db scan). I want the inherited models to run the validation rule in the repo (or service) layer and send a ValidationResult to the (MVC) client-layer.
The problem is one of inheritance.
public interface IUniqueTitle
{
int Id { get; set; }
string Title { get; set; }
// This is a "multi-client, one database" solution.
// Data is isolated using SiteId
int SiteId { get; set; }
}
// Models such as "MemberClub" and "Assessment" will inherit from this
public class EntityUniqueTitle : IUniqueTitle
{
public int Id { get; set; }
public int SiteId { get; set; }
public string Title { get; set; }
}
// This class will be used in production
public class MemberClub : EntityUniqueTitle
{
}
I wrote an extension method that to check to see if the Title field is unique based on the SiteId
public static bool IsUniqueTitle<T>(this IQueryable<T> items, T currentEntity) where T : IUniqueTitle
{
return items.Where(
item => item.Id != currentEntity.Id // INCASE UPDATING OBJECT
& item.SiteId == currentEntity.SiteId
& item.Title == currentEntity.Title)
.Count() == 0;
}
Here is where I get stuck. Where should I put the validation?
I can put in the Repo but can't figure out how to fire the ValidationResult upon Save
public class RepoUniqueTitle<T> : IRepoUniqueTitle<T> where T : EntityUniqueTitle, new()
{
protected readonly DbContext c;
public Repo(IDbContextFactory f) { c = f.GetContext(); }
public void Insert(T o)
{
if (!c.Set<T>().IsUniqueTitle(o))
{
// ***********************
// PROBLEM HERE, HOW DO I STOP AND SEND A VALIDATIONRESULT TO THE CLIENT?
// IF POSSIBLE, AUOTMATIC WHEN MODEL.ISVALID IS CALLED
// code from base repo class for reference
// if (o is IUniqueTitleForSite)
// IoC.Resolve<IRepoUniqueTitle<T>>().Validate(o);
}
else
c.Set<T>().Add(o);
}
}
I am hoping there is a validation solution that:
Models can inherit from a base Entity
Can make db calls to the inherited entity's collection
Works with ValidationResult so it can be cleanly integrated into MVC Tier
Called during Model.isValid if possible
Note: I am using ProDinner as a basis for an "n-tier code-first EF mvc/wf" solution.
Sorry, a lot of this is new to me. Any help you can provide would be greatly appreciated!
If you have separate business logic layer you should place the validation to that layer. Otherwise why to have that layer if you don't use it to execute business rules?
Anyway unique check is tricky because there is a delay between your query and actual saving of data and another thread can insert the item with the same title during that delay. You should place unique index on Title and SiteId to enforce uniqueness in the database. In such case former problem will result in the exception which you must handle somehow but it is probably better then data duplicity.