EntityFramework 4.3.1代码优先导航属性映射和可见性

时间:2012-08-03 16:05:26

标签: entity-framework many-to-many code-first navigational-properties

对于一个简短的问题,这是一个很长的介绍,抱歉!!

我正在使用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)很容易假设角色授权逻辑可能很复杂(现在已经有一些已经授权的验证,但可能是任何验证,对吗?),这是完全有效的事情。一个很好的老式领域模型。但是......根据Requirements for Creating POCO Proxies,角色集合public virtual ICollection<Role> Roles {get; set;}需要公开,至少是吸气者。这意味着Action类的任何客户端都可以绕过任何验证逻辑来​​添加或删除角色。 是的,我想要延迟加载,更改跟踪,工作,所以我确实需要创建代理。

Regardlles,我开始测试一些方法,使我能够使这个属性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上下文类不是其中之一。

因此我需要尝试从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),这就是得到的东西。但不,它不起作用,它编译了courrse,但在运行时,我得到了异常,就像“这个expresion必须是一个有效的属性,如obj =&gt; obj.MyProperty”。

好吧,它需要是一个“直接”属性,它需要可以访问de DBContext类。我决定将我的属性设置为protected internal并将我的DBContext类移动到我的Domain类库(其中定义了所有实体),我真的不喜欢那么多,但是我更喜欢让我的属性可以被所有人访问。我的财产看起来像这样:

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和即使它不是公共财产,也要搜索该物业。这将允许非公共导航属性,在我看来,它一直是允许一个整洁的设计对象domian。

也许我在这里错过了什么。

Thanx很多。

1 个答案:

答案 0 :(得分:1)

你的问题问为什么对模型构建者的受保护属性的限制,我不确定为什么会这样。但是,如果您想要解决方法,我已成功实施this blog的解决方案。

您将使用表达式更新您的实体,以便模型构建者可以找到它:

  protected virtual ICollection<Role> Roles { get; set; }

        public class PropertyAccessExpressions
        {
            public static readonly Expression<Func<User, ICollection<Role>>> ID = x => x.Roles;
        }

然后你的建造者应该能够找到这个:

  modelBuilder.Entity<Domain.Action>()
            .HasMany(action => action.Roles)