这是一个简短问题的长篇介绍,对不起!
我正在使用 EF 4.3.1 Code First,我有以下模型
public class Action
{
protected Action()
{ }
public virtual int ActionID { get; protected set; }
[Required]
[StringLength(DataValidationConstants.NameLength)]
public virtual string Name {get; set;}
[StringLength(DataValidationConstants.DescriptionLength)]
public virtual string Description { get; set; }
public virtual ICollection<Role> Roles { get; set; }
public virtual void AuthorizeRole(Role role)
{
if (IsRoleAuthorized(role))
throw new ArgumentException("This role is already authorized", "role");
Roles.Add(role);
}
}
public class Role
{
protected Role()
{ }
public virtual int RoleID { get; protected set; }
[Required]
[StringLength(DataValidationConstants.NameLength)]
public virtual string Name { get; set; }
[StringLength(DataValidationConstants.DescriptionLength)]
public virtual string Description { get; set; }
}
还有我的 DBContext 类,在其他一些类库中定义,具有多对多映射:
public class myDB : DbContext
{
public DbSet<Domain.Action> Actions { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Domain.Action>()
.HasMany(action => action.Roles)
.WithMany()
.Map(map => map.ToTable("AuthorizedRoles"));
}
}
所以,这行得通。但是,如果注意到该方法Action.AuthorizeRole(Role role)
很容易假设角色授权逻辑可能很复杂(现在一些已经授权的验证,但可能是任何验证,对吧?),这在一个好的老式域上是完全有效的模型。但是...根据创建 POCO 代理的要求,角色集合public virtual ICollection<Role> Roles {get; set;}
需要公开,至少是吸气剂。这意味着 Action 类的任何客户端都可以添加或删除角色,绕过任何验证逻辑。是的,我想要延迟加载、更改跟踪等工作,所以我确实需要创建代理。
问候,我开始测试一些方法,使我能够将此属性设为public virtual ICollection<Role> Roles {get; set;}
非公共属性,以便稍后测试代理创建。由于代理生成了我自己的类的子类,并且由于我信任我的继承者而不是我的客户,所以我决定将属性设为protected
这样protected virtual ICollection<Role> Roles {get; set;}
。但是,当然,我得到了一个编译错误就行了
modelBuilder.Entity<Domain.Action>()
.HasMany(action => action.Roles)
.WithMany()
.Map(map => map.ToTable("AuthorizedRoles"));
因为现在该属性受到保护,不能在 Action 类或其继承者之外访问,当然myDB
context 类不是其中之一。
所以我需要尝试在myDB
不公开它(属性)的情况下从类中访问属性。我虽然反思。我的上下文类看起来像这样:
public class myDB : DbContext
{
public DbSet<Domain.Action> Actions { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Domain.Action>()
.HasMany(action => ExtractPropertyValue<ICollection<Role>>(action, "Roles"))
.WithMany()
.Map(map => map.ToTable("AuthorizedRoles"));
}
protected virtual TProperty ExtractPropertyValue<TProperty>(object instance, string propertyName)
{
if(instance == null)
throw new ArgumentNullException("instance", "Can't be null");
if (string.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Can't be null or white spaced", "propertyName");
Type instanceType = instance.GetType();
PropertyInfo propertyInfo = instanceType.GetProperty(propertyName, BindingFlags.NonPublic);
return (TProperty)propertyInfo.GetValue(instance, null);
}
}
注意新方法ExtractPropertyValue
,在多对多映射指令上调用它。这灵魂工作对吗?HasMany 方法需要一个函数,该函数接收一个 Action 并返回一个 ICollection 的某物(在这种情况下为 Role),这就是得到的。但是不,它不起作用,它当然可以编译,但是在运行时,我得到了类似“这个表达式必须是像 obj => obj.MyProperty 这样的有效属性”的异常。
好吧,它需要是一个“直接”属性,并且它需要可以访问 de DBContext 类。我决定将我的属性设置为受保护的内部并将我的 DBContext 类移动到我的域类库(其中定义了所有实体),我真的不太喜欢它,但我更喜欢让我的属性被所有人访问. 我的财产看起来像这样:
protected internal virtual ICollection<Role> Roles { get; set; }
我的 DBContext 类是这样的,就像我第一次拥有它一样,只是它现在定义在与所有实体相同的类库中:
public class mrMantisDB : DbContext
{
public DbSet<Domain.Action> Actions { get; set; }
public DbSet<Role> Roles { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Domain.Action>()
.HasMany(action => action.Roles)
.WithMany()
.Map(map => map.ToTable("AuthorizedRoles"));
}
}
这很好用。因此,现在唯一需要检查的是代理的创建,即延迟加载和更改跟踪。作为受内部保护而不是公共保护的财产,我担心它可能不起作用,但是是的,它确实起作用了,而且一切顺利,真的。
现在,我的问题/请求来了。如果导航属性实际上不需要公开才能创建代理,则受保护就足够了(我将内部排除在外,因为我认为这只会影响使用该属性进行关系映射的能力),为什么要限制在表达式上提取 HasMany 方法的属性,或者更好,因为我知道属性必须是被映射类型的属性而不是随机集合,为什么没有 HasMany 的重载,它采用字符串 propertyName 和搜索财产本身,即使它不是公共财产。这将允许具有非公共导航属性,在我看来,这将一直允许一个整洁设计的对象域。
也许我在这里遗漏了一些东西。
非常感谢。