0

Can someone explain how should I map an entity with a Array or List of primitive values using EntityTypeConfiguration. I have the follow entity and enum:

public class PermissionForm
{
   public int Id {get; set;}

   public string Name {get; set;}

   public PermissionItem[] Permissions {get; set;}
}

public enum PermissionItem : short
{
   EDIT = 1,
   SHARE = 2,
   ADMIN = 3
}

I was searching for something similiar what I got trying to understand how it works, but got no luck.

I'd like that Tables in Database was something like:

| PermissionForm |
| -------------- |
| Id             |
| Name           |

|  PermissionForm_PermissionItems |
| ------------------------------- |
| PermissionFormId                |
| PermissionItem_Value            |

Till now I got the follow code, but I don't think is close to the right:

public PermissionForMap()
{
    this.ToTable("PermissionForm").HasKey(p => p.Id);

    this.Property(p => p.Name).HasColumnName("Name").HasMaxLength(30);
    this.Map(map =>
    {
       map.ToTable("PermissionsFormPermissionItem");
       map.Requires(p => p.Id);
       map.Properties(p => p.PermissionItems);
    });
}
Dener
  • 121
  • 2
  • 8
  • 1
    Why don't you just [use your enum with `[Flags]` attribute](https://stackoverflow.com/questions/21021138/save-flag-enums-in-sql-database-and-ef6-is-this-possible) and remove the array from your entity? You may always get and set your multiple attributes using bitwise operations. This is supported in EF since 6.X and will also avoid the use of joins. – Federico Dipuma Oct 02 '17 at 20:33
  • Are you stuck with EF4? Later versions (5, if memory serves) added enum support. – Gert Arnold Oct 02 '17 at 20:36
  • @FedericoDipuma oh thanks man, I didnt know this. First time using this EF. Write your answer ther to mark it as right. But even though, I'd like to know how to make that mapping. – Dener Oct 02 '17 at 20:53

1 Answers1

2

Enum with [Flags]

The most efficient way to support your scenario is to use an Enum which has the FlagsAttribute on it (this requires at least EntityFramework 5):

public class PermissionForm
{
   public int Id {get; set;}

   public string Name {get; set;}

   public PermissionItem Permissions {get; set;}
}

[Flags]
public enum PermissionItem : short
{
   EDIT = 1,
   SHARE = 2,
   ADMIN = 4 // note that each value must be a power of 2
}

Which doesn't need any particular mapping:

public PermissionForMap()
{
    this.ToTable("PermissionForm").HasKey(p => p.Id);

    this.Property(p => p.Name).HasColumnName("Name").HasMaxLength(30);
    this.Property(p => p.Permissions).HasColumnName("Permissions"); // this is redundant, it's just to replicate your behavior
}

This way you may manage permissions using just the bitwise operators & (AND), | (OR), ^ (XOR) and ~ (complement).

This will simplify your model and avoids the use of another table (which also requires joins inside your queries).

Example

Get all entities which have SHARE permission:

var query = _dbContext.PermissionForms.Where(p => p.PermissionItem & PermissionItem.SHARE > 0);

If you use EntityFramework 6.1+ you may also use the built-in method HasFlag:

var query = _dbContext.PermissionForms.Where(p => p.PermissionItem.HasFlag(PermissionItem.SHARE));

Set multiple permission to an entity:

var permission = new PermissionForm
{
    Name = "MyName",
    Permissions = PermissionItem.EDIT | PermissionItem. SHARE // this has EDIT and also SHARE
};

Table mapping

If you really want to map your relationship to a table (which, in my opinion, is useless and inefficient for your scenario), you need to create a class, whici is supported by EntityFramework as an entity:

public class PermissionForm
{
   public PermissionForm()
   {
       Permissions = new HashSet<PermissionEntity>();
   }

   public int Id {get; set;}

   public string Name {get; set;}

   public virtual ICollection<PermissionEntity> Permissions {get; set;}
}

public class PermissionEntity
{
    public int PermissionFormId { get; set; }

    public PermissionItem PermissionItem { get; set; }

    public virtual PermissionForm PermissionForm { get; set; }
}

public enum PermissionItem : short
{
   EDIT = 1,
   SHARE = 2,
   ADMIN = 3
}

And map it as any other navigation property for your model:

public class PermissionForMap : EntityTypeConfiguration<PermissionForm>
{
    public PermissionForMap()
    {
        this.ToTable("PermissionForm").HasKey(p => p.Id);

        this.Property(p => p.Name).HasColumnName("Name").HasMaxLength(30);
        this.HasMany(p => p.Permissions) 
            .WithRequired(e => e.PermissionForm)
            .HasForeignKey(e => e.PermissionFormId);
    }
}

public class PermissionEntityMap : EntityTypeConfiguration<PermissionEntity>
{
    public PermissionEntityMap()
    {
        ToTable("PermissionEntities")
            .HasKey(e => new { e.PermissionFormId, e.PermissionItem }):
    }
}
Federico Dipuma
  • 17,655
  • 4
  • 39
  • 56
  • Of course all under the assumption that OP is able to upgrade their EF version. Maybe you could add (to your already very complete answer) that flagged enums can be queried by `HasFlags`. – Gert Arnold Oct 03 '17 at 06:03
  • Understood! So I can't mapping an array of primitive directly, right? But really thanks, I'm using Flag. It's easy and so clean. Awesome! – Dener Oct 03 '17 at 11:35
  • @GertArnold Thanks, just edited the answer after your suggestion. – Federico Dipuma Oct 03 '17 at 12:13