EF randomly sees wrong property type

2020-07-16 02:53发布

问题:

We're using EF code first with EF 6.1.3 and SimpleInjector 3.1.0 and we're randomly getting exceptions similar to this:

The 'IsDeleted' property on 'Location' could not be set to a 'System.Int32' value. You must set this property to a non-null value of type 'System.Boolean'.
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.ErrorHandlingValueReader`1.GetValue(DbDataReader reader, Int32 ordinal)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper.GetPropertyValueWithErrorHandling[TProperty](Int32 ordinal, String propertyName, String typeName)
at lambda_method(Closure , Shaper )
at System.Data.Entity.Core.Common.Internal.Materialization.Coordinator`1.ReadNextElement(Shaper shaper)
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.SimpleEnumerator.MoveNext()
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source)

In this case the Location table's IsDeleted column is bit / not null. And the Property on the Location entity is definitely a non-nullable boolean. I've checked through the entire history of the entity in our git repository and this property has never been anything else but a boolean, and the current dll on the server is correct, so it's not like the issue is caused by the wrong code/dll.

What's really strange is that most of the time our site works just fine, I would expect this the exception to occur every time. What's weirder is that we've seen this same type of error occur on 3 other entities (also seemingly randomly). In each case it was a different property and data type. For example:

The 'Description' property on 'SysGroup' could not be set to a 'System.Boolean' value. You must set this property to a non-null value of type 'System.Int32'.

The 'SysGroupId' property on 'SysInfo' could not be set to a 'System.String' value. You must set this property to a non-null value of type 'System.Int32'.

The 'Id' property on 'BaseEntity' could not be set to a 'System.Guid' value. You must set this property to a non-null value of type 'System.Int32'.

About the last one... Every entity we have inherits from BaseEntity, which has a single non-nullable int property named 'Id'. We don't use any Guid's anywhere either. So if BaseEntity was indeed using a Guid for the Id property, then every query everywhere would fail. This last exception happened when the code was navigating a property, like this:

var desc = SysInfoInstance.SysGroup.Description;

Any ideas?

The Location class is esentially:

public class Location : 
    BaseEntity
{
    ...
    public virtual bool IsDeleted { get; set; }
}
public abstract class BaseEntity
{
    public virtual int Id { get; set; }
}

回答1:

I'm not sure why your IsDeleted property is marked as virtual.

There is no other reason to make properties virtual. Navigation properties are marked as virtual for lazy loading and scalar properties are marked as virtual for change tracking.

IsDeleted doesn't require the virtual keyword

You could create another property as follows and not map it to you db. But this shouldn't be necessary.

public int Deleted { get; set; }

[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public bool IsDeleted
{
    get { return Deleted == 1; }
    set { Deleted = value ? 1 : 0; }
}


回答2:

Had the same problem. Looks like some type of EF bug connected to store procedures. Are you using them in your app?

In my case the problem was, that I've changed the structure (added columns to the middle) of the table in DB, while the stored procedure (which get the data from that DB) was already added to EF. After that the same exception appears each time I've tried to call the stored procedure. The exception looks really weird and I lost a lot of time to clear out whats wrong.

The solution was to move the new columns to the end of the table. It fixed everything. Looks like the EF`s store procedures somehow save the structure of data and when the table structure changes it throws very weird and wrong exception.

Another solution was to remove the stored procedure from EF and put it back.