fluent nhibernate - Many to Many mapping with attr

2019-02-07 15:38发布

问题:

I have 2 master tables which are linked by a map table as below

User [UserId,Name]

Resource [ResourceId,Name]

UserResourceMap [UserId,ResourceId,AccessLevel]

How would the Resource and User ClassMap with AccessLevel as a resource attribute look?

My Domain classes look like this

public class User
{
    public virtual int UserId { get;protected set; }
    public virtual string Name { get;set; }
}
public class Resource
{
    public virtual int ResourceId { get;protected set; }
    public virtual string Name { get;set; }
    public virtual string AccessLevel { get;set; }//Issue-populate this using fluent
}

How can I use fluent to map the accessLevel attribute in the below code.

public class UserMap : ClassMap<User>
    {
        public UserMap()
        {
            Table("User");
            Id(x => x.Key);
            Map(x=>x.Name);
        }
    }

public class ResourceMap : ClassMap<Resource>
    {
        public ResourceMap()
        {
            Table("Resource");
            Id(x => x.Key);
            Map(x=>x.Name);//Need some Map Here to make a hasManyToMany Map with attribute
        }
    }

回答1:

Your domain model does not seem to match your database model - the Resource class has the property AccessLevel (i.e. one AccessLevel per Resource) but in the DB model AccessLevel is a column on the map table (i.e. one AccessLevel per User-Resource relation).

Assuming the DB model is the correct model one (fairly straightforward) way of mapping this would be to introduce a class like this.

public class UserResource {

  public virtual int UserResourceId { get; protected set; }
  public virtual User User { get; set; }
  public virtual Resource { get; set; }
  public virtual string AccessLevel { get; set; }
}

and map it in this way:

public class UserResourceMap : ClassMap<UserResource> {

  public UserResourceMap() {

    Table("UserResourceMap");
    Id(x => x.UserResourceId);
    References(x => x.User).UniqueKey("UniqueUserAndResource");
    References(x => x.Resource).UniqueKey("UniqueUserAndResource");
    Map(x => x.AccessLevel);
  }
}

If you want bidirectional associations you could also add a Collection property on User and/or Resource and map these with HasMany(...).Inverse(). Of course, this kind of mapping would introduce a new UserResourceId column in the UserResourceMap table (using a composite key consisting of User and Resource would mitigate that).

Another solution would be to add an EntityMap association. If the association is owned by User it would be a Dictionary<Resource, string> property. Something like this might do the trick:

public class User {
  public virtual int UserId { get; protected set; }
  public virtual string Name { get; set; }
  public virtual Dictionary<Resource, string> Resources { get; set; } // Resource -> AccessLevel
}

public class UserMap : ClassMap<User> {

  public UserMap() {

    Table("User");
    Id(x => x.UserId);
    Map(x => x.Name);
    HasMany<Resource, string>(x => x.Resources).AsEntityMap().Element("AccessLevel");
  }
}


回答2:

As you've correctly identified in your database schema, this isn't a pure many-to-many relationship - it's two one-to-many relationships as the intermediate table has an attribute (the access level).

I therefore think your domain is missing an entity - there doesn't appear to be any relationship in your model between a user and the resources they can access.

How about something like this:

public class User
{
    public virtual int Id { get;protected set; }
    public virtual string Name { get;set; }
    public virtual ICollection<UserResource> UserResources { get; set;}
}

public class UserResource
{
    public virtual int Id { get; protected set; }
    public virtual User User { get; set;}
    public virtual Resource Resource { get; set;}
    public virtual string AccessLevel { get; set;}
}

public class Resource
{
    public virtual int Id { get;protected set; }
    public virtual string Name { get;set; }
}

And mappings like:

public class UserMap : ClassMap<User>
{
    public UserMap()
    {
        Id(x => x.Id);
        Map(x => x.Name);
        HasMany(x => x.UserResource)
            .AsSet()
            .Inverse()
            .Cascade.AllDeleteOrphan();
    }
}

public class UserResourceMap : ClassMap<UserResource>
{
    public UserResourceMap()
    {
        Table("UserResourceMap");
        Id(x => x.Id);
        References(x => x.User).Not.Nullable();
        References(x => x.Resource).Not.Nullable();
        Map(x => x.AccessLevel);
    }
}

public class ResourceMap : ClassMap<Resource>
{
    public ResourceMap()
    {
        Cache.ReadOnly();

        Id(x => x.Id);
        Map(x => x.Name);
    }
}