Given this extremely simple model:
public class MyContext : BaseContext
{
public DbSet Foos { get; set; }
public DbSet Bars { g
Just had the same problem in EF 6.1.2. To solve this your class should be like the following:
public class Foo {
public int Id { get; set; }
public int Data { get; set; }
public int BarId { get; set; }
public virtual Bar Bar { get; set; }
}
As you can see, the "Required" attribute is not needed, because the Bar property is already required since the BarId property is not nullable.
So if you wanted the Bar property to be nullable, you would have to write:
public class Foo {
public int Id { get; set; }
public int Data { get; set; }
public int? BarId { get; set; }
public virtual Bar Bar { get; set; }
}
I found the following post that had an answer for the same problem:
The cause of this problem is that in RC and RTM validation no longer lazy loads any properties. The reason this change was made is because when saving a lot of entities at once that have lazy loaded properties validation would get them one by one potentially causing a lot of unexpected transactions and crippling performance.
The workaround is to explicitly load all validated properties before saving or validating by using .Include(), you can read more on how to do this here: http://blogs.msdn.com/b/adonet/archive/2011/01/31/using-dbcontext-in-ef-feature-ctp5-part-6-loading-related-entities.aspx
My take on this is that is a pretty crappy proxy implementation. While unnecesarily walking the object graph and retriveing lazy-loaded properties is naturally something to be avoided (but apparently overlooked in Microsoft's first incarnation of EF), you shouldn't have to need to go un-proxying a wrapper to validate that it exists. On second thoughts, I'm not sure why you need to go walking the object graph anyway, surely the change tracker of the ORM knows what objects require validation.
I'm not sure why the problem exists, but I'm sure I wouldn't be having this problem if I was using say, NHibernate.
My 'workaround' - What I've done is define the Required nature of the relationship in a EntityTypeConfiguration class, and removed the Required attribute. This should make it work fine. It means that you will not validate the relationship, but it will fail the update. Not an ideal result.
Ok, here is the real answer =)
if you have a property (like your Bar
) noting a FK (ForeignKey
), you can also have the corresponding FK field in your model so if we only need the FK and not the actual Bar
we don't need it to go to the database:
[ForeignKey("BarId")]
public virtual Bar Bar { get; set; }
public int BarId { get; set; }
Now, to answer your question, what you can do to make the Bar
as Required
is to flag the BarId
property as required, but not the Bar
itself:
[ForeignKey("BarId")]
public virtual Bar Bar { get; set; }
[Required] //this makes the trick
public int BarId { get; set; }
this works like a charm =)
Transparent workaround to ignore error on unloaded references
In your DbContext
, override ValidateEntity
method to remove validation error on references that are not loaded.
private static bool IsReferenceAndNotLoaded(DbEntityEntry entry, string memberName)
{
var reference = entry.Member(memberName) as DbReferenceEntry;
return reference != null && !reference.IsLoaded;
}
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
if (result.IsValid || entityEntry.State != EntityState.Modified)
{
return result;
}
return new DbEntityValidationResult(entityEntry,
result.ValidationErrors
.Where(e => !IsReferenceAndNotLoaded(entityEntry, e.PropertyName)));
}
Pros :
I know it's a bit late... However, ill post this here. Since i too got horribly annoyed with this. Just tell EF to Include
the required field.
Notice the SMALL change
using (var context = new MyContext())
{
var foo = context.Foos.Include("Bar").Find(id);
foo.Data = 2;
context.SaveChanges(); //Crash here
}
Since this is still a problem in EF 6.1.1 I thought I would provide another answer that may suit some people, depending on their exact model requirements. To summarize the issue:
You need to use a proxy for lazy loading.
The property you are lazy loading is marked Required.
You want to modify and save the proxy without having to force-load the lazy references.
3 is not possible with the current EF proxies (either of them), which is a serious shortcoming in my opinion.
In my case the lazy property behaves like a value type so its value is provided when we add the entity and never changed. I can enforce this by making its setter protected and not providing a method to update it, that is, it must be created through a constructor, eg:
var myEntity = new MyEntity(myOtherEntity);
MyEntity has this property:
public virtual MyOtherEntity Other { get; protected set; }
So EF will not perform validation on this property but I can ensure it is not null in the constructor. That is one scenario.
Assuming you do not want to use the constructor in that way, you can still ensure validation using a custom attribute, such as:
[RequiredForAdd]
public virtual MyOtherEntity Other { get; set; }
The RequiredForAdd attribute is a custom attribute that inherits from Attribute not RequiredAttribute. It has no properties or methods apart from its base ones.
In my DB Context class I have a static constructor which finds all the properties with those attributes:
private static readonly List<Tuple<Type, string>> validateOnAddList = new List<Tuple<Type, string>>();
static MyContext()
{
FindValidateOnAdd();
}
private static void FindValidateOnAdd()
{
validateOnAddList.Clear();
var modelType = typeof (MyEntity);
var typeList = modelType.Assembly.GetExportedTypes()
.Where(t => t.Namespace.NotNull().StartsWith(modelType.Namespace.NotNull()))
.Where(t => t.IsClass && !t.IsAbstract);
foreach (var type in typeList)
{
validateOnAddList.AddRange(type.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(pi => pi.CanRead)
.Where(pi => !(pi.GetIndexParameters().Length > 0))
.Where(pi => pi.GetGetMethod().IsVirtual)
.Where(pi => pi.GetCustomAttributes().Any(attr => attr is RequiredForAddAttribute))
.Where(pi => pi.PropertyType.IsClass && pi.PropertyType != typeof (string))
.Select(pi => new Tuple<Type, string>(type, pi.Name)));
}
}
Now that we have a list of properties we need to check manually, we can override validation and manually validate them, adding any errors to the collection returned from the base validator:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
return CustomValidateEntity(entityEntry, items);
}
private DbEntityValidationResult CustomValidateEntity(DbEntityEntry entry, IDictionary<object, object> items)
{
var type = ObjectContext.GetObjectType(entry.Entity.GetType());
// Always use the default validator.
var result = base.ValidateEntity(entry, items);
// In our case, we only wanted to validate on Add and our known properties.
if (entry.State != EntityState.Added || !validateOnAddList.Any(t => t.Item1 == type))
return result;
var propertiesToCheck = validateOnAddList.Where(t => t.Item1 == type).Select(t => t.Item2);
foreach (var name in propertiesToCheck)
{
var realProperty = type.GetProperty(name);
var value = realProperty.GetValue(entry.Entity, null);
if (value == null)
{
logger.ErrorFormat("Custom validation for RequiredForAdd attribute validation exception. {0}.{1} is null", type.Name, name);
result.ValidationErrors.Add(new DbValidationError(name, string.Format("RequiredForAdd validation exception. {0}.{1} is required.", type.Name, name)));
}
}
return result;
}
Note that I am only interested in validating for an Add; if you wanted to check during Modify as well, you would need to either do the force-load for the property or use a Sql command to check the foreign key value (shouldn't that already be somewhere in the context)?
Because the Required attribute has been removed, EF will create a nullable FK; to ensure you DB integrity you could alter the FKs manually in a Sql script that you run against your database after it has been created. This will at least catch the Modify with null issues.