I came across this problem where I should allow different sets of validation when saving a model. Usually the validation depends on how the user wants to save the data. For example, if the user wants to save his data as "Draft", I should allow some fields to be blank. If not "Draft", I should put more restrictions.
class Form
{
public int ID { get; set; }
[Required("Title is required.")]
public string Title { get; set; }
[Required("Body is required.")]
public string Body { get; set; }
public bool IsDraft { get; set; }
}
In the code above, I want the validation for body to work only if IsDraft is false.
How do you create multiple sets of validation? Or how to properly create conditional validation in Entity Framework?
Data annotations are not only used for validation but also to make your code in sync with the database. I.e. in EF Code First, if you remove Required field, then your database must allow NULL to be inserted in matching column.
Therefore you can do the following approach:
Create your entity model class to have as lowest requirements as possible
Create your view/DTO model class that will implement IValidatableObject interface
Implement IValidatableObject interface and do conditional validation there
IValidatableObject interface allows your class to be used by Validator (MSDN on Validator) from System.ComponentModel.DataAnnotations namespace, in the same way the validation is done via annotation attributes.
Interface implementation should look like this:
public class FormDto: IValidatableObject
{
public int ID { get; set; }
[Required("Title is required.")]
public string Title { get; set; }
[Required("Body is required.")]
public string Body { get; set; }
public bool IsDraft { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if(!IsDraft && string.IsNullOrWhiteSpace(Body)) yield return new ValidationResult("Body is required.", new string [] {"Body"});
if(!IsDraft && string.IsNullOrWhiteSpace(Title)) yield return new ValidationResult("Title is required.", new string [] {"Title"});
}
}
If your view / DTO model is going through i.e. MVC pipeline, this interface will be invoked automatically. If from any reason you notice it is not invoked (I don't know the structure of your solution), you can plug this code somewhere in your process pipeline and it will invoke validation / throw exception:
public static class IValidatableObjectExtensions
{
public static void SelfValidate(this IValidatableObject model)
{
ValidationContext ctx = new ValidationContext(model);
Validator.ValidateObject(model, ctx, true);
}
}
Related
I am trying to figure out a way to develop a database model using Entity Framework that does not do updates or deletes. The business requirements want the complete history of all changes that are made to each record in the system, for analysis reasons. So instead I want to always modify by inserting a new record to the database.
Is there a clean way to get Entity Framework to do that? Or am I going to be jumping through a lot hoops to get this sort of behavior. The basic model is pretty simple, some stuff, like constructors, removed since they don't add much to the discussion:
public class Container
{
public Guid Id { get; private set; }
public ICollection<Container> RelatedContainers { get; private set; }
public ICollection<Item> Items { get; private set; }
}
public class Item
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Value { get; private set; }
}
Basically you need to override SaveChanges() method in DbContext. In your method get all the objects that have the EntityState Deleted or Modified and set the status UnChanged.
public class YourDbContext:DbContext{
public override int SaveChanges(){
foreach ( var ent in this.ChangeTracker
.Entries()
.Where(p =>p.State == System.Data.EntityState.Deleted
p.State == System.Data.EntityState.Modified))
{
ent.State =System.Data.EntityState.Unchanged;
}
}
}
Is there any way to determine when actual items are added to an ICollection<> virtual member when it is being loaded from a query?
Hopefully the code below will be able to demonstrate my point!!
public class DbAppointment
{
public DbAppointment()
{
}
public virtual int AppointmentId { get; set; }
public virtual string Subject { get; set; }
public virtual string Body { get; set; }
public virtual DateTime Start { get; set; }
public virtual DateTime End { get; set; }
private ICollection<DbExceptionOcurrence> exceptionOcurrences;
public virtual ICollection<DbExceptionOcurrence> ExceptionOcurrences
{
get { return exceptionOcurrences; }
set
{
exceptionOcurrences = value;
}
}
}
and
public class DbExceptionOcurrence
{
public DbExceptionOcurrence()
{
}
public virtual int ExceptionId { get; set; }
public virtual int AppointmentId { get; set; }
public virtual DateTime ExceptionDate { get; set; }
public virtual DbAppointment DbAppointment { get; set; }
}
The code for loading these is
Database.SetInitializer(new ContextInitializer());
var db = new Context("EFCodeFirst");
// when this query executes the DbAppointment ExceptionOcurrenes (ICollection) is set
// but for some reason I only see this as an empty collection in the virtual setter DbAppointment
// once the query has completed I can see the ExceptionOcurrences
var result = db.Appointments.Include(a => a.ExceptionOcurrences).ToList();
In the DbAppointment ICollection ExceptionOcurrences setter for each item I need to perform some addtional logic. The problem I am having is that I only seem to have this information once the DbAppointment objects have already been created.
Is there any way to determine when the items have been added so I can perform my logic.
Cheers
Abs
Apparently the behaviour you are seeing means that Entity Framework creates and fills the collection similar to this:
// setter called with empty collection
dbAppointment.ExceptionOcurrences = new HashSet<DbExceptionOcurrence>();
// only getter gets called now
dbAppointment.ExceptionOcurrences.Add(dbExceptionOcurrence1);
dbAppointment.ExceptionOcurrences.Add(dbExceptionOcurrence2);
dbAppointment.ExceptionOcurrences.Add(dbExceptionOcurrence3);
//...
I had hoped that you can use the ObjectMaterialized Event (can be registered with DbContext like in this example: https://stackoverflow.com/a/4765989/270591, the EventArgs contain the materialized entity) but unfortunately the documentation says:
This event is raised after all scalar, complex, and reference
properties have been set on an object, but before collections are
loaded.
It looks that you have to run through the result collection after it has been loaded completely and call some method on each result item which performs your custom logic on the navigation collection.
Maybe another option is create a custom collection type that implements ICollection<T> with an event handler for the Add method and allows you to hook in some logic everytime a new item is added. Your navigation collection in the model class would have to be of that type. Maybe even ObservableCollection<T> is fine for this purpose.
I am working with EF 4.3 code first and using WCF Data services (5.0 just released) to expose the data over HTTP
I find that if I call a service operation from a browser I am able to get related entities back, but when I consume the service operation in a client app I am not getting back the related entities.
I have been researching this isuse it seems the EF enables lazy loading whn using the virtual key word when referencing a an ICollection, this some how prevents WCF data services from returing realted entities - is this true
If i browse locally and put a break point on my getUsersByName method I can see the related group entity but when it comes over the wire to a client app the gropup entity is missing.
Is there a configuration to enable this.
Thanks
eg
public partial class Group
{
public Group()
{
this.Users = new HashSet<User>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupID { get; set; }
[Required(ErrorMessage = "Please enter a group name")]
[StringLength(50, ErrorMessage = "The group name is too long")]
public string GroupName { get; set; }
[Required]
public System.DateTime CreatedDate { get; set; }
[Required]
public bool Admin { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public partial class User
{
public User()
{
this.Groups = new HashSet<Group>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Required(ErrorMessage="Please enter a username")]
[StringLength(50,ErrorMessage="Username is too long")]
public string UserName { get; set; }
[Required(ErrorMessage="Please enter an email address")]
[RegularExpression(".+\\#.+\\..+",ErrorMessage="Please enter a valid email address")]
public string Email { get; set; }
[Required]
public System.DateTime CreatedDate { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public partial class TestContext : DbContext
{
public Test()
: base("name=TestEntities")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Tell Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
public DbSet<Group> Groups { get; set; }
public DbSet<User> Users { get; set; }
}
[ServiceBehavior(IncludeExceptionDetailInFaults=true)]
[JSONPSupportBehavior]
public class TestSVC : DataService<TestContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.All);
config.SetServiceOperationAccessRule("*", ServiceOperationRights.All);
config.SetServiceActionAccessRule("*", ServiceActionRights.Invoke);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
config.UseVerboseErrors = true;
}
[WebGet]
public User GetUserByName(string userName)
{
var user = (from u in this.CurrentDataSource.Users
where u.UserName == userName
select u).FirstOrDefault();
return user;
}
Actually regardless of what you do on the server side with the entity WCF DS will not return expanded entities by default. It's a feature. The reason is size of the message on the wire.
The client can request this behavior though (and that should work without making any modifications to the EF entities on the server side).
Let's assume you have a service operation GetProducts. You can issue a query like ~/GetProducts?$expand=Category which will include the Category entities in the result.
You also noted that the client doesn't get to see these, but you do see them in the browser. So are you already using the $expand? If that's the case the problem is solely on the client. Make sure that you request the results using the $expand on the client as well (depends on what code you're using, there's an Expand method in the LINQ on the client for this). And then you can use Fiddler to see if the client actually gets the results back the way you want. If that's the case and you still don't get the results in your client code, it might be due to MergeOptions. Try setting DataServiceContext.MergeOption to OverwriteChanges and try again (but be sure that you're aware of what this setting does).
Try removing the initialization of the navigation properties in the constructor because it will create problems with the proxy objects.
It's almost impossible to use lazy loading with serialization because when the serializer access the navigation properties the related entities will be loaded. This will lead to load whole database. So you need to disable lazy loading and use Include to load whatever you want or you can use some DTO with lazy loading enabled .
I'm using MVC3 VS2010 with EF4.1, I have created my DB using SQL Server and I import it to the MVC3 Web Application.
I have a challenge here, when I come to Update Model from Database I do lost all my models files modifications, for example if I'm using attributes in some models for validation or so all that is overwritten with the new model properties.
Is there anyway to Update Model from Database without losing models' information?
OR
where should I define validation on my models instead of using the models' files directly?
Update: As this is still relatively popular, I have created a blog post on this.
http://jnye.co/Posts/19/adding-validation-to-models-created-by-entity-framework-database-first-c
If you want to validate your models, and not use viewModels, use partial classes to define validation attributes. For example:
Say you have a model like
public class User {
public string Name { get; set; }
}
If you wanted to put a string length validator on it you would need to create a partial class and utilise the MetadataTypeAttribute (this lives in System.ComponentModel.DataAnnotations)
The following classes should be defined in their own separate file, NOT put in the same file as your auto generated models.
[MetadataTypeAttribute(typeof(UserMetadata))]
public partial class User {
}
You then define your validation in the UserMetadata class as follows
public class UserMetadata{
[StringLength(50)]
public string Name {get; set;}
}
EDIT
I just found this article which explains the solution in a little more detail
http://themonitoringguy.com/tips-tricks/validating-microsoft-entity-framework-objects-c-mvc/
No, the files will be regenerated every time.
All the classes are defined as partial so you can easily add DataAnnotations using the MetadataTypeAttribute.
Let's say you have a User class defined as follow:
public partial class User {
public string Name {get;set;}
}
Create a IUser interface
public interface IUser {
[Required]
[DisplayName("User name")]
string Name {get;set;}
}
And then extend the User class to specify that IUser will be used as metadata.
[MetadataType(typeof(IUser))]
public partial class User {} //Empty class body
The first rule of any designer is: It it generates any code you can't modify it because it will be completely deleted next time you update anything in the designer.
All generated classes are partial so you can create your own partial part and put your custom logic there. You obviously can't add attributes to properties defined in auto generated part. In case of data annotations it is possible either through buddy classes or by custom T4 template which will contain your own logic to decide which data annotation should be added during code generation. Both scenarios are mostly considered as a bad practice because you should have separate view model per view with validation needed exactly for that view.
Check the namespace of the MainClass is same as Partial, and have the same Attributes. That is my solution.
example:
Metadata: Create this everywhere u want
public class FormMetadata
{
public int Id { get; set; }
public string Description { get; set; }
public Nullable<bool> IsEnable { get; set; }
public Nullable<System.DateTime> CreationDate { get; set; }
public int CompanieId { get; set; }
public string RegularExpression { get; set; }
public virtual ICollection<Field> Fields { get; set; }
[JsonIgnore]
public virtual Company Company { get; set; }
}
MainClass
namespace Transactions.Model
{
public partial class Form
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Form()
{
this.Fields = new HashSet<Field>();
}
public int Id { get; set; }
public string Description { get; set; }
public Nullable<bool> IsEnable { get; set; }
public Nullable<System.DateTime> CreationDate { get; set; }
public int CompanieId { get; set; }
public string RegularExpression { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<Field> Fields { get; set; }
public virtual Company Company { get; set; }
}
}
Partial To Use the MetadataType
namespace Transactions.Model
{
[MetadataTypeAttribute(typeof(FormMetadata))]
public partial class Form
{
}
}
If you have problems to Create a Class Partial in the same NameSpace? Don't worry:
Create a Folder
Create the Class Partial in this folder
Change Namespace at the same of MainClass
Imagine I have an entity called Product and a repository for it:
public class Product
{
public int Id { get; set; }
public bool IsHidden { get; set; }
}
public class ProductRepository
{
public ObservableCollection<Product> AllProducts { get; set; }
public ObservableCollection<Product> HiddenProducts { get; set; }
}
All products contains every single Product in the database, while HiddenProducts must only contain those, whose IsHidden == true. I wrote the type as ObservableCollection<Product>, but it does not have to be that.
The goal is to have HiddenProducts collection be like a proxy to AllProducts with filtering capabilities and for it to refresh every time when IsHidden attribute of a Product is changed.
Is there a normal way to do this? Or maybe my logic is wrong and this could be done is a better way?
Ended up on CollectionView/CollectionViewSource stuff.