简体   繁体   中英

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.

EG: 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:

  • objectInstance : a Proxy derived from the Entity that I have to update, that I (have to) find previously
  • values : a dictionary with the property name and the new value of the properties I have to update. It could be filled with every property, or just with some of them.
  • properties : the array of properties of the class I previously found via reflections

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));
   }
}

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.

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM