Deadpool
Deadpool

Reputation: 1121

Check if a property of an Entity is marked as IsRequired() via reflections and EF Core

THE QUESTION

If I have an array of properties of a certain Entity and I'm iterating through them, is there any way to check if the reflected type property that I am iterating in each cycle is configured as .IsRequired() on its corresponding Entity?

EXAMPLE

This question has to be intended especially for string properties, as in most of value types, if a db property allows null values, then it is mapped by EF Core's Scaffolding operation as nullable type.

E.G.: a nullable int is mapped as int?, while a not-nullable one is mapped as int.

If I iterate through that mapped Entity's properties, to check if that property I am iterating now is a nullable one, I just have to check if myproperty.PropertyType == typeof(int?) but...what in case of a string type?

Is there any way to check if it's marked as an .IsRequired() property?

MY CODE SO FAR

In my code I have the following function, which is supposed to receive as params:

This function is supposed to iterate through the properties array and for each property, if the new value is contained inside the dictionary, to set its new value on the instance of the class.

private static bool SetValues(Object objectInstance, Dictionary<string, object> values, PropertyInfo[] properties)
{
    bool edited = false;
    foreach (var item in values)
    {
        var temp = properties.Where(w => w.Name.ToLower() == item.Key.ToLower()).FirstOrDefault();
        if (temp != null)
        {
            edited = true;
            if (temp.PropertyType == typeof(string))
            {
                //here it is where I would like to do the above mentioned check
                temp.SetValue(objectInstance, Convert.ToString(item.Value));
            }
            if (temp.PropertyType == typeof(int) || temp.PropertyType == typeof(int?))
            {
                temp.SetValue(objectInstance, Convert.ToInt32(item.Value));
            }
            if (temp.PropertyType == typeof(long) || temp.PropertyType == typeof(long?))
            {
                temp.SetValue(objectInstance, Convert.ToInt64(item.Value));
            }
            if (temp.PropertyType == typeof(decimal) || temp.PropertyType == typeof(decimal?))
            {
                temp.SetValue(objectInstance, Convert.ToDecimal(item.Value));
            }
            if (temp.PropertyType == typeof(bool) || temp.PropertyType == typeof(bool?))
            {
                temp.SetValue(objectInstance, Convert.ToBoolean(item.Value));
            }
            if (temp.PropertyType == typeof(DateTime) || temp.PropertyType == typeof(DateTime?))
            {
                temp.SetValue(objectInstance, Convert.ToDateTime(item.Value));
            }
        }
    }
    return edited;
}

Here's how I get "objectInstance":

var objectInstance = _context.Query(TableType).Where("Id = @0", rowKey).FirstOrDefault();

Where "Query" is an Extension:

public static IQueryable Query(this DbContext context, Type entityType) =>
            (IQueryable)((IDbSetCache)context).GetOrAddSet(context.GetDependencies().SetSource, entityType);

And... an example of what I mean with a IsRequired()-marked property of an Entity, to avoid misunderstandings:

 public void Configure(EntityTypeBuilder<MyTable> builder)
 {
    //[a lot of properties above here...]
    builder.Property(e => e.Code)
                    .IsRequired()  //that's it!
                    .HasMaxLength(50)
                    .IsUnicode(false);
    //...
 }

WHAT I WOULD LIKE TO ACHIEVE

On the //here it is where I would like to do the above mentioned check comment's position, I would like to check if (pseudocode):

if(temp.IsRequired())
{
   if(String.IsNullOrWhiteSpace(Convert.ToString(item.Value)))
   {
       temp.SetValue(objectInstance, "");
   }
   else
   {
       temp.SetValue(objectInstance, Convert.ToString(item.Value));
   }
}
else
{
   if(String.IsNullOrWhiteSpace(Convert.ToString(item.Value)))
   {
       temp.SetValue(objectInstance, null);
   }
   else
   {
       temp.SetValue(objectInstance, Convert.ToString(item.Value));
   }
}

Upvotes: 2

Views: 3053

Answers (2)

Ivan Stoev
Ivan Stoev

Reputation: 205759

The proper way of doing that in EF Core is not using reflection, but the EF Core provided metadata. Which means your method should have access (receive as argument) the DbContext (or at least IModel returned by DbContext.Model property).

Once you have it, you could use FindEntityType method to get the IEntityType containing the associated metadata with the entity class, then some of the FindProperty method overloads to get the IProperty containing the metadata associated with that property, and finally check the IsNullable property:

Gets a value indicating whether this property can contain null.

which correctly takes into account both data type, data annotations and fluent configuration.

Something like this:

private static bool SetValues(DbContext db, Object objectInstance, 
    Dictionary<string, object> values, PropertyInfo[] properties)
{
    var entityType = db.Model.FindEntityType(objectInstance.GetType());
    bool edited = false;
    foreach (var item in values)
    {
        var property = entityType.FindProperty(item.Key);
        if (property != null)
        {
            var propertyType = property.ClrType;
            bool isRequired = !property.IsNullable;
            // do something ...     
        }
    }
}

This eliminates the need PropertyInfo[] properties parameter.

Update: In order to work with proxy classes, instead of FindEntityType use the FindRuntimeEntityType method.

Gets the entity that maps the given entity class, where the class may be a proxy derived from the actual entity type. Returns null if no entity type with the given CLR type is found or the entity type has a defining navigation.

Upvotes: 8

Fabien Sartori
Fabien Sartori

Reputation: 235

Yes, you should do like this

 [IsNotNullable]
    [IsPK]
    [IsIdentity]
    [SequenceNameAttribute("Id")]
    [Required]
    public Int32 Id
    {
        get
        {
            return _Id;
        }
        set
        {
            _Id = value;
        }
    }
var t = typeof(YourClass);
var pi = t.GetProperty("Id");
var attr = (Required[])pi.GetCustomAttributes(typeof(Required), false);
if (attr.Length > 0) {
    // Use attr[0], you'll need foreach on attr if MultiUse is true
}

Upvotes: 1

Related Questions