Entity Framework, unmapped property and Dynamic Data - entity-framework

I'm using an Entity Framework data model to drive a Dynamic Data website for use by users to update data.
One of the entities contains a non-nullable string property (Description). In the database, one of the rows has an empty Description (not null but an empty string). When I try to update the Description I get the following validation error: "This property cannot be set to a null value".
If I manually update the Description in the database and then edit the property, it works as expected. But as soon as I change the Description in the database back to an empty string, the validation error occurs. The error happens on Description's setter.
So I've tried adding an additional string property called CustomDescription which basically wraps Description, made Description a ScaffoldColumn(false) in the entity's metadata and added the new property to the entity's metadata.
[ScaffoldColumn(true)]
public string CustomDescription
{
get { return this.Description; }
set {
if (value == null)
{
value = string.Empty;
}
this.Description = value;
}
}
However what do I need to add to this property in order to get it to display on the dynamic data site?

Problem is that old value was empty string in Non-Nullable field.
By default framework is converting it to null.
To fix the error just add the following attribute to your field:
[DisplayFormat(ConvertEmptyStringToNull = false)]
public object Description { get; set; }

In the corresponding Metadata class, just refernce it as you would an actual field:
[MetadataType(typeof(MyClassMetadata))]
public partial class MyClass
{
[ScaffoldColumn(true)]
public string CustomString
{
return "foo";
}
}
public class MyClassMetadata
{
[Display(Name = "Custom")]
public object CustomString { get; set; }
}

Related

ServiceStack AutoQuery - Check for null in nullable DateTime-field

I user ServiceStack autoquery to load information. I have a class like this one:
public class QueryItem: QueryDb<Item>
{
public string Name { get; set; }
public DateTime? BirthdayNotEqualTo { get; set; }
}
As written in the documentation, I should be able to receive all items that are not null in the Birthday column like this:
QueryResponse<Item> item = jsonServiceClient.Get(new QueryItem {
BirthdayNotEqualTo = null
});
However, I receive all items, regardless of the null-filter above. How can I change that? The values in the database are correctly set to null.
It's not possible to send a null value using the ServiceClient. A null value, like default(DateTime?) means no value, so there's no "null filter" sent and your query is the same and indistinguishable from sending an Empty QueryItem DTO.
You'd need to use a Custom AutoQuery Implementation or a Customizable Query like:
[QueryDbField(Template = "{Field} IS NOT NULL", Field = "Birthday")]
public bool? BirthdayIsNotNull { get; set; }

Have specific property configuration for all types that implement an interface except for one

I have a number of Types that inherit from the Interface 'IEventReportElement', which exposes the property 'IdEventReport':
public interface IEventReportElement
{
long Id { get; set; }
long? IdEventReport { get; set; }
}
This is a nullable property, as I'm not always able to fill it out properly right away, but should be not nullable in the database.
Thats why I have added the line
modelBuilder.Types<IEventReportElement>().Configure(x=>x.Property(y=>y.IdEventReport).IsRequired());
to my OnModelCreating method in the DbContext.
However, the Type 'Position' has to implement this interface, but should NOT have the column for property 'IdEventReport' in the database, but instead a column for the property 'IdParent' it exposes.
public class Position : BOBase, IEventReportElement
{
public long? IdEventReport
{
get { return IdParent; }
set { IdParent = value; }
}
public long? IdParent { get; set; }
}
and the section in the modelBuilder
modelBuilder.Entity<Position>().Property(x => x.IdParent).IsRequired();
modelBuilder.Entity<Position>().Ignore(x => x.IdEventReport);
However, this throws an exception already when trying to Create the database:
System.InvalidOperationException: The property 'IdEventReport' is not a declared property on type 'Position'. Verify that the property has not been explicitly excluded from the model by using the Ignore method or NotMappedAttribute data annotation. Make sure that it is a valid primitive property.
Though this may be valid, is it not possible to override the given Type configuration for a specific type? Do I have to add the line .IsRequired() to every other type that implements this interface, or is there another way to overcome this?
I did find a solution, however it's a not so nice one. I did it by modifying the line of the type configuration to
modelBuilder.Types<IEventReportElement>().Where(x=>x.Name!="Position").Configure(x=>x.Property(y=>y.IdEventReport).IsRequired());
If you just want that the column has a different name in the database, use HasColumnName.
For access to IdParent in the C# model, use [NotMapped] to tell EF to ignore this property when creating the DB.
public class Position : BOBase, IEventReportElement {
public long? IdEventReport {get; set; }
[NotMapped]
public long? IdParent {
get { return IdEventReport ; }
set { IdEventReport = value; }
}
}
modelBuilder.Entity<Position>().Property(x => x.IdEventReport).HasColumnName("IdParent");
As a side note: why are you implementing an interface that you don't want to use? Maybe you can split the interface in smaller parts and only implement what you are going to use.

Add related database entry in Azure Mobile Services controller

In my Azure Mobile Service I have a controller class UserController : TableController<User> and in it is a get method:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public SingleResult<User> GetUser(string id)
{
return Lookup(id);
}
I want to record each time a user is accessed and so I add a simple type to the model:
public class UserVisit : Microsoft.WindowsAzure.Mobile.Service.EntityData
{
public string VisitingUser { get; set; }
public DateTime TimeOfVisit { get; set; }
}
and include the property public DbSet<UserVisit> UserVisits { get; set; } in my VCollectAPIContext : DbContext class (and update the database with a code-first migration).
To add a UserVisit to the database when a user id is queried I change my controller method to
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
But the SaveChangesAsync fails with a System.Data.Entity.Validation.DbEntityValidationException. Digging around in the exception's EntityValidationErrors property I find that the problem is "The Id field is required."
That's a little odd. The Id field is one of the properties in the base-class Microsoft.WindowsAzure.Mobile.Service.EntityData that I would expect to be added automatically on insert. No matter, I can add it and several of the other base-class's properties thus:
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { Id = Guid.NewGuid().ToString(), Deleted = false, VisitingUser = id, TimeOfVisit = DateTime.UtcNow, CreatedAt = DateTimeOffset.Now };
context.UserVisits.Add(userVisit);
await context.SaveChangesAsync();
return Lookup(id);
}
This time I get a System.Data.Entity.Infrastructure.DbUpdateException because we "Cannot insert the value NULL into column 'CreatedAt'". It was not null in the call to Add. So CreatedAt has been set to null somewhere outside my code and then the insert fails as a result!
I also tried setting up an EntityDomainManager<UserVisit> userVisitDomainManager; instance variable in the controller's initializer, and then rewriting my controller get method as
// GET tables/User/48D68C86-6EA6-4C25-AA33-223FC9A27959
public async Task<SingleResult<User>> GetUser(string id)
{
var userVisit = new UserVisit { VisitingUser = id, TimeOfVisit = DateTime.UtcNow };
await userVisitDomainManager.InsertAsync(userVisit);
return Lookup(id);
}
That fails with the same message, "Cannot insert the value NULL into column 'CreatedAt'"
How should I perform the seemingly simple task of inserting a related data item within my controller method?
The solution is likely similar to this answer. I'm guessing that your migration is not using the Mobile Services SqlGenerator so some of the custom SQL settings aren't getting applied. What that means is that:
Id doesn't get a default value of NEWID() -- this explains your "Id field is required" error.
CreatedAt doesn't get a default value of SYSUTCDATETIME() -- this, combined with the [DatabaseGenerated] attribute on EntityData.CreatedAt, explains the "NULL CreatedAt" error.
Try updating your migration according to the link above and see if that works for you.
To fix the problem of "The Id field is required" following brettsam's instructions.
Add this in your model:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[TableColumn(TableColumnType.Id)]
public new string Id { get; set; }
It will auto generate a GUID when you add an entity.

EF6 proxy's reference is sometimes null when entity's IValidatableObject.Validate method is called

An EF6 proxy has a reference that is sometimes null when my entity's IValidatableObject.Validate method is called by the DbContext's SaveChangesAsync method.
Running the same exact code multiple times results in different behavior. If I check my stock's Sku property (i.e. stock.Sku == null) outside of the Validate method it always returns a materialized entity. If I do not do that and only check this.Sku within the Validate method then this.Sku will sometimes be null for the exact same entity. And by "exact same entity" I mean that I am testing the one stock multiple times that has the same Id and SkuId across all test runs. I'm not creating a new stock here or changing the value of its SkuId property. The one thing I am doing is calling the stock's ChangeQuantity method and then saving changes.
My best guess at this point is that once save changes is called all entity and reference materialization is frozen. If the Sku property has not already been accessed at least once then it will be null and remain null when the DB context's save changes code calls my object's Validate method.
My questions are: Why is this happening and why can't I depend on that property being available to be lazy loaded at anytime?
public abstract class StockBase : RecordBase
{
// Snipped //
[Required, Display(Name = "SKU")]
public Guid SkuId { get; set; }
[Display(Name = "SKU")]
public virtual Sku Sku { get; protected set; }
[Required]
public int Quantity { get; private set; }
[DataType("StockActions")]
public virtual ICollection<StockAction> Actions { get; private set; }
public void ChangeQuantity(DateTime logged, Guid loggedById, int changeInQuantity, string notes = null)
{
TrackChange(logged, loggedById);
Quantity += changeInQuantity;
Actions.Add(new StockAction(logged, loggedById, changeInQuantity));
}
}
public class StandardStock : StockBase, IValidatableObject
{
// Snipped //
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// Right here is where `this.Sku` is sometimes null!
if (Sku.IsExpiringStock)
{
throw new InvalidOperationException("Standard stock must have a non-expiring SKU.");
}
yield break;
}
}
Unfortunately, lazy loading is disabled during validation when performed through Entity Framework.
https://msdn.microsoft.com/en-us/data/gg193959#Considerations
It looks like you are using lazy loading to populate the Sku object. When you test the Sku property manually, you are forcing the lazy loading to run, and the value gets materialised. If you're already doing something with the context at the time you're expecting it to be loaded, or the context has been disposed, then it will remain as null.
If you always need to populate this property, consider explicitly loading it when you load the entity. This will stop your lazy loading problem, and also eliminate a trip to the database.

Entity Framework error when submitting empty fields

VS 2010 Beta 2, .NET 4.
In my ASP.NET MVC 2 application, when I submit a form to an action method that accepts an object created by the entity framework, I get the following error:
Exception Details: System.Data.ConstraintException: This property cannot be set to a
null value.
Source Error:
Line 4500: OnTextChanging(value);
Line 4501: ReportPropertyChanging("Text");
Line 4502: _Text = StructuralObject.SetValidValue(value, false);
Line 4503: ReportPropertyChanged("Text");
Line 4504: OnTextChanged();
The property is called "Text" and is of type "text NOT NULL" in MS SQL 2008.
My action will check if the value is nullorempty, if it is, a model error will be added, but I get the error as soon as I submit the form.
Are you binding directly to the entity? Sure looks like it. So you have two choices:
Write a custom model binder which translates null -> empty string.
Bind to an edit model which allows nulls instead, and then change this to empty string when you copy the values to the entity in the action.
I'd choose #2, personally. I think you should always use view/edit models, and this is a great example of why.
I was having the same problem. I looked around and found a work around here. It describes the problem as being caused by the EF validation taking place before the Required field validation. It also shows how we can work around this problem by using a [DisplayFormat] Tag. Hope this will help you.
Here's the link to the question and the workaround:
Server-side validation of a REQUIRED String Property in MVC2 Entity Framework 4 does not work
Is this an issue with the MVC2 and Entity Framework 4 or is this by design? It appears that validation of EF properties works fine for datetime non-nullable (required) fields and data type validation of numeric versus string fields is working without having to use ViewModels.
I recreated the issue using with a simple FOOBAR table using a single, non-nullable varchar(50) column called barName in slq 2008. I generated the EF model from that database and quickly added a controller and a CREATE view for the FOOBAR entity. If I try to POST to the CREATE action without entering in a value for the property barName, VS steps into an exception within the designer.cs file of the model (just like the one above). When, I try to step past the exception, the validation message shows up on the form and the field is highlighted in pink.
It seems like something is not firing in the correct sequence. Because the exception occurs before VS steps into the HTTPPOST CREATE method.
I found the code from the ASP.Net MvcMusicStore sample helpful. http://mvcmusicstore.codeplex.com/releases/view/44445#DownloadId=119336
It appears that binding to the ViewModel fixes the issue.
namespace MvcMusicStore.ViewModels
{
public class StoreManagerViewModel
{
public Album Album { get; set; }
public List<Artist> Artists { get; set; }
public List<Genre> Genres { get; set; }
}
}
........
namespace MvcMusicStore.Models
{
[MetadataType(typeof(AlbumMetaData))]
public partial class Album
{
// Validation rules for the Album class
[Bind(Exclude = "AlbumId")]
public class AlbumMetaData
{
[ScaffoldColumn(false)]
public object AlbumId { get; set; }
[DisplayName("Genre")]
public object GenreId { get; set; }
[DisplayName("Artist")]
public object ArtistId { get; set; }
[Required(ErrorMessage = "An Album Title is required")]
[StringLength(160)]
public object Title { get; set; }
[DisplayName("Album Art URL")]
[StringLength(1024)]
public object AlbumArtUrl { get; set; }
[Required(ErrorMessage = "Price is required")]
[Range(0.01, 100.00, ErrorMessage="Price must be between 0.01 and 100.00")]
public object Price { get; set; }
}
}
}
Ashish Shakya's answer helped me. I added this attribute to the property and now it works.
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
So it looks like this:
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
public global::System.String MyProperty
{
get
{
return _MyProperty;
}
set
{
OnMyPropertyChanging(value);
ReportPropertyChanging("MyProperty");
_MyProperty = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("MyProperty");
OnMyPropertyChanged();
}
}
Import the namespace:
using System.ComponentModel.DataAnnotations;
And add the attribute property [Required]
[Required]
public global::System.String MyProperty
{
get
{
return _MyProperty;
}
set
{
OnMyPropertyChanging(value);
ReportPropertyChanging("MyProperty");
_MyProperty = StructuralObject.SetValidValue(value, false);
ReportPropertyChanged("MyProperty");
OnMyPropertyChanged();
}
}
Thus ModelState.IsValid equals false, showing error message in the validation and will not fail on the server with Null.
I had the same problem and fixed it by making false to true like this:
Line 4502:
_Text = StructuralObject.SetValidValue(value, false);
I just had the same problem myself, and came here to find the solution. However, the answer can be enhanced.
Svavar's and HackITMngr were on the right track, however combining both gives the best outcome. You don't want to go decorating the generated classes, as you risk losing your custom changes upon modifications to the EF model.
[MetadataType(typeof(MyTableMetaData))]
public partial class MyTable
{
// Validation rules for the Album class
public class MyTableMetaData
{
[DisplayFormat(ConvertEmptyStringToNull = false, NullDisplayText="")]
public string MyTextProperty { get; set; }
}
}
To settle any arguments between the two. I'd say Svavar's was the direct answer, HackITMngr was the enhancement.
Works great for me!
I set StoreGeneratedPattern property as Computed for each field and it solved the problem for me.