如何避免强制转换但又能传入正确的类型?

4
我正在使用Entity Framework v4。我尝试实现一些逻辑来验证我的实体在保存之前,通过重写SaveChanges方法。同时,我对实体使用了POCO模式。
以下是获取修改和新实体列表的代码:
var entities = (ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
                .Select(ose => ose.Entity).OfType<IValidatable>();
foreach(var entity in entities)
{
        var validator = validatorProvider.GetValidator(entity);
        var errors = validator.Validate(entity);
        if (errors.Count() > 0)
        {
            throw new Exception("A validation error");
        }
}

以下是我的GetValidator方法的代码:

private static IValidator<TObject> GetValidator<TObject>(TObject obj) where TObject : IValidatable
{
    var objType = obj.GetType();
    var validatorType = Type.GetType("SmsPortal.Validation." + objType.Name + "Validator");
    var varValidator = Activator.CreateInstance(validatorType);
    return (IValidator<TObject>)varValidator;
}

问题在于我收到了一个失败的提示,内容如下:
Unable to cast object of type 'Blah.Validation.ConcreteValidator' to type 'Blah.Validation.IValidator`1[Blah.Validation.IValidatable]'

唯一能够消除这个错误的方法是先将对象转换为正确的类型,但我不想强制进行所有的类型转换。

以下是我定义的IValidator接口:

public interface IValidator<TEntity>
        where TEntity : IValidatable
{
    IEnumerable<ValidationError> Validate(TEntity entity);
}

好的,现在让我为你解释一下我的疯狂想法背后的原因。我试图遵循SRP,不希望我的对象能够自我验证。所以我的IValidatable接口只是一个标记。我已经尝试过使用和不使用这个标记,但没有任何区别。我也不想让我的单元测试变得臃肿,尽管我知道我可以为验证和实际实体编写单独的单元测试。

其次,我相当懒,不想编写映射器等等。此外,我希望有更多的约定优于配置,如果提供了验证器,它将被使用。

我在很多地方都使用了泛型。我喜欢它所提供的功能,但我并不是专家,现在它正在咬我。

有什么方法可以避免这种情况吗?有没有办法避免强制转换对象,使运行时可以找出要将其转换成什么?如果有帮助的话,我正在使用Ninject的依赖注入。

更新: 根据要求,具体的验证器如下:

public class ConcreteValidator : IValidator<SomeObject>
{
    public IEnumerable<ValidationError> Validate(SomeObject entity)
        {
            if (string.IsNullOrEmpty(entity.Name))
                    yield return new ValidationError { Message = "Name is mandatory", Property = "Name" };

                if (entity.Name != null && entity.Name.Length > 50)
                    yield return new ValidationError { Message = "Name must be less than 50 characters", Property = "Name" };
    }
}

请同时发布ConcreteValidator的定义。 - Péter Török
@Peter,我已经发布了代码。 :) - uriDium
SomeObject是否扩展了IValidatable接口? - Matt Wonlaw
@mlaw。是的,就是这样。它只是一个标记。没有可以实现的方法。我尝试过将类型约束设置为IValidatable和普通类。都不起作用。运行时无法推断出类型。 - uriDium
4个回答

1
我建议您为验证器使用服务定位器,根据验证器的类型参数获取验证器。您可以在此处找到如何执行此操作的示例。 另外,我确实不太理解您的代码正在发生什么,因为以下代码片段可以正常工作。因此,请发布使用的所有类的完整代码片段。
public interface IValidator<T> where T: IObj 
{

}

public class PersonValidator : IValidator<Person>
{

}

public static class Validators
{
    public static IValidator<TObject> GetValidator<TObject>(TObject obj)
        where TObject : IObj
    {
        var t = obj.GetType();
        var name = string.Format("{0}.{1}Validator", t.Namespace, t.Name);
        return (IValidator<TObject>) 
            Activator.CreateInstance(Type.GetType(name));
    }

}

[TestFixture]
public class ValidatorsTest
{
    [Test]
   public void TestPersonValidator()
    {
        var pValidator = Validators.GetValidator(new Person());

    }
}

你遇到的问题在以下内容中

var entities = (ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
            .Select(ose => ose.Entity).OfType<IValidatable>();

foreach(var entity in entities) { var validator = validatorProvider.GetValidator(entity); var errors = validator.Validate(entity); if (errors.Count() > 0) { throw new Exception("验证错误"); } }

在这里,当您调用validatorProvider.GetValidator(entity)时,实体的类型为IValidator,因此您正在调用validatorProvider.GetValidator<IValidator>(entity),但是您真正想要做的是validatorProvider.GetValidator<TEntity>(entity),您需要使用反射调用泛型方法,动态指定要调用的方法。

最后,为了使用反射调用适当的方法,您最好将方法声明更改为GetValidator<TObject>()并执行以下操作:

foreach(var entity in entities)
{
        var validator = validatorProvider.GetType().GetMethod("GetValidator").MakeGenericMethod(entity.GetType()).Invoke(validatorProvider, null );
        var errors = validator.Validate(entity);
        if (errors.Count() > 0)
        {
            throw new Exception("A validation error");
        }
}

最后修订:

....
foreach(var entity in entities)
{
        GetType()
              .GetMethod("ValidateObj")
              .MakeGenericMethod(entity.GetType())
              .Invoke(this, null );

}
....
....
public void ValidateObj<TEntity>(TEntity obj) where TEntity : IValidatable {
     var errors = validatorProvider.GetValidator<TEntity>().Validate(obj);
     if (errors.Count() > 0 ) throw new ValidationException(obj, errors);
}

@vittore。我认为你没有读懂我的问题。我已经几乎完全做到了这一点。除了当你从实体框架中获取某些东西时,它是一个实体对象。实体对象包装原始对象并公开它。但是,我必须强制转换为正确的类型,以便运行时可以正确推断验证器的正确类型参数。我不知道运行时的类型。我希望它是动态的。 - uriDium
1
@uriDium,假设你有一个Person类,在foreach循环中,你需要将其转换为IValidatable类型,因此在foreach循环内部调用GetValidator<IValidatable>(person)方法,该方法返回的是IValidator<IValidatable>而不是IValidator<Person>,这清楚吗? - vittore
@uriDium:你需要进行类型转换,是的,对于foreach中的代码我很抱歉。 - vittore
@uriDium:问题在于,在.NET 4之前,协变和逆变不支持泛型类型... - vittore
@uriDium,如果您使用foreach循环中的代码创建一个全新的通用方法,并以与getvalidator相同的方式调用该通用方法,那么这将更容易。这样,当访问方法时,您只需要使用反射,而不需要使用反射获取正确的验证器并对其进行验证。 - vittore
显示剩余4条评论

0

查看这个问题的答案,可以很好地解释这里发生了什么。

如果你将ConcreteValidator更改为以下内容,应该就可以了:

public class ConcreteValidator : IValidator<SomeObject>, IValidator<IValidatable>

0
一行代码就能解决你的问题: 动态实体 = entry.Entity;

0

SomeObject 是否继承自 TObject?即使是这样,在 Java 中,IValidator<SomeObject> 也不是 IValidator<TObject>。不过,你的代码看起来像是 C#,我不知道泛型在那里是如何工作的。

我认为你应该在帖子中添加适当的语言标签,因为泛型肯定是与语言相关的。


你是100%正确的,这是C#而不是Java。我按照你的建议更新了标签。 - uriDium

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接