【问题标题】:How to set properties on a generic entity?如何在通用实体上设置属性?
【发布时间】:2021-01-14 10:32:16
【问题描述】:

我想检查一个实体是否有 3 个属性。 CreatedDate、ModifiedDate 和 ModifiedBy。

现在我只是在我的对象上下文的 SaveChanges() 方法中硬编码我知道的那些。

例如:

bool newEntity = (entry.State == EntityState.Added);

if (type == typeof(Foo))
{
  var r = entry.Entity as Foo;
  if (r != null)
  {
    if (newEntity) 
      r.CreatedDate = DateTime.Now;
    r.ModifiedDate = DateTime.Now;
    r.ModifiedBy = HttpContext.Current.User.Identity.Name;
  }
}

我知道可以使用类似下面的代码来检查一个对象是否有某种方法:

public static bool HasMethod(this object objectToCheck, string methodName)
{
    var type = objectToCheck.GetType();
    return type.GetMethod(methodName) != null;
} 

但是我如何在不直接转换实体的情况下获得这些属性?

我该怎么做:

if (HasMethod(entry.Entity))
      entry.Entity.ModifiedDate = DateTime.Now;

我正在使用 ASP.Net MVC 4。

【问题讨论】:

  • 使用反射。 PropertyInfo.
  • 也许dynamic 可以用于此,但我必须说我不确定。
  • 在标题中添加标签是没有意义的。请阅读meta.stackexchange.com/q/19190/147072了解更多信息。
  • 如果您有权访问这些实体的声明,更好的是创建一个包含这些属性的接口,将该接口分配给所有也包含它们的实体,然后检查该对象是否实现那个界面。
  • 对于这样的事情,我倾向于将这些属性放在我的所有数据库映射实体都继承自的基类上,这样我就知道它们会在那里。然后您可以添加where T : EntityBase 并直接从您的Add<T>()Update<T>() 方法访问属性。我知道在你的情况下这可能是不可能的,但值得一提。

标签: c# entity-framework


【解决方案1】:

您可以使用以下方法。如果存在,它将设置该属性。在每次调用时使用GetType 可能会导致一些开销,需要优化。

private bool TrySetProperty(object obj, string property, object value) {
  var prop = obj.GetType().GetProperty(property, BindingFlags.Public | BindingFlags.Instance);
  if(prop != null && prop.CanWrite) {
    prop.SetValue(obj, value, null);
    return true;
  }
  return false;
}

用法

TrySetProperty(entry.Entity, "ModifiedDate", DateTime.Now);

【讨论】:

  • 非常好。我将对它进行基准测试,看看它的表现如何。无论哪种方式都非常聪明。
  • @Smith:实际查找类型和属性需要相当长的时间,但如果你设法将它们缓存在静态字典中,速度方面的效率会大大提高。跨度>
  • (imo) 如果您要在方法名称前添加“Try”,则应使返回类型 bool 与 int.TryParse(以及许多其他方法)等方法保持一致
【解决方案2】:

您可以使用反射 - 已经有几个人提到过 - 或者您可以创建一个界面。如果您使用自动生成的实体,它们是使用 partial 关键字定义的,因此您可以在同一个项目中创建另一个类文件,并为其提供相同的命名空间和类定义,然后您就可以让它实现您的接口。然后在上面发布的代码中,检查对象是否实现了您的接口,如果是,则转换为它,然后设置值。

接口的优点是您不使用反射(这可能是一项昂贵的操作),而且,您创建的任何未来实体都将通过实现您的接口自动工作。

如果您的实体属性与您的接口不完全匹配,您可以显式实现该接口,这将处理任何命名违规行为。

示例:假设您定义了一个接口IContainAuditProperties,并且您的所有适用实体都实现了该接口,您可以在循环所有新/更改实体的块内执行以下操作:

var entity = entry.Entity as IContainAuditProperties;
if(entity != null)
{
  entity.CreatedDate = DateTime.Now;
  entity.ModifiedDate = DateTime.Now;
  //etc.
}

【讨论】:

  • 我使用部分类来实现接口没有问题,因为我的元数据属性已经有了很多。我的问题是如何在不直接引用实体的情况下进行投射。
  • 我同意布赖恩的观点。接口绝对是这里的必经之路。
  • 这应该是最佳答案。除非您更喜欢更慢的代码和更多的代码,否则使用反射是个坏主意。
  • +1 表示“这应该是公认的答案”。几乎总是更好的做法胜过最明显的解决方案(而且肯定是使用反射)。
【解决方案3】:

您可以使用以下代码检查对象是否具有具有特定名称的公共可设置属性:

public static bool HasWritableProperty(this object objectToCheck, string propertyName)
{
  var type = objectToCheck.GetType();
  //get a property info for the property, but only if it is a public instance property
  var pi = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public)
  //return false if no propery is found
  if (pi==null) return false;
  //get the set method for the property
  var setter = pi.GetSetMethod();
  //if it's null the property is not writable
  return (setter != null);
}

但是,这不是一个非常有效的代码,因为它使用了很多反射。

如果你走这条路,我会记住这段代码的结果,所以每次检查最多运行一次。

我在我的项目中实际做的是(我使用 EF Code First),有一个 BaseEntity 类,它具有常见的属性,并让具体实体继承自 BaseEntity 类。然后我有一个FillEntityMetadata 方法,或多或少是这样的:

protected void FillEntityMetadata(BaseEntity entity, bool isUpdate = false)
{
  // Set audit data.
  if (!isUpdate)
  {
    entity.CreatedBy = CurrentUser.ID;
    entity.CreatedOn = DateTime.Now;
    entity.IsActive = true;
  }
  entity.LastModifiedBy = CurrentUser.ID;
  entity.LastModifiedOn = DateTime.Now;
}

请注意,要使其工作,您还可以使用接口IBaseEntity,它的功能与此相同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-07
    • 2013-04-25
    • 1970-01-01
    • 2019-02-20
    相关资源
    最近更新 更多