Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to always attach Enumeration classes? #12150

Closed
gojanpaolo opened this issue May 26, 2018 · 4 comments
Closed

How to always attach Enumeration classes? #12150

gojanpaolo opened this issue May 26, 2018 · 4 comments

Comments

@gojanpaolo
Copy link

gojanpaolo commented May 26, 2018

I have been using Enumeration classes as described here. My issue is I always need to attach any of my Enumeration class when adding a related entity to the database. For example:

public class Order
{
  public Order(OrderStatus orderStatus)
  {
    OrderStatus = orderStatus;
  }
  public int OrderId { get; private set; }
  public OrderStatus OrderStatus { get; private set; }
}

public class OrderStatus : Enumeration
{
  public static OrderStatus Submitted = new OrderStatus(1, "Submitted");
  public static OrderStatus Cancelled = new OrderStatus(2, "Cancelled");
  public OrderStatus(int id, string name) : base(id, name) { }
  public static IEnumerable<OrderStatus> GetAll() => new[] { Submitted, Cancelled };
}

public class OrderingContext :DbContext
{
  public DbSet<Order> Orders { get; set; }
  public DbSet<OrderStatus> OrderStatus { get; set; }
  public OrderingContext(DbContextOptions<OrderingContext> options) : base(options) { }
  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
    modelBuilder.Entity<OrderStatus>().HasKey(o => o.Id);
    modelBuilder.Entity<Order>().HasOne(o => o.OrderStatus);
  }
}

//usage
using (var context = new OrderingContext(options))
{
  context.Database.EnsureCreated();

  //seed
  context.AddRange(OrderStatus.GetAll());
  context.SaveChanges();
}

using (var context = new OrderingContext(options))
{
  //context.OrderStatus.Attach(OrderStatus.Submitted); //uncomment to attach
  context.Orders.Add(new Order(OrderStatus.Submitted)); //throws exception if OrderStatus not attached
  context.SaveChanges();
}

Is there any way to always attach Enumeration classes to the context?

@ajcvickers
Copy link
Member

@gojanpaolo As mapped above, OrderStatus is an entity type. As such having these "always attached" is equivalent to having reference data pre-loaded into the context. I created #12206 to track this.

However, in the case above, it would likely be more appropriate to not map the enumeration class as an entity type, but to instead use a value converter to transform into a string. Something like:

public class Order
{
    public Order(OrderStatus orderStatus)
    {
        OrderStatus = orderStatus;
    }
    
    public int OrderId { get; private set; }
    public OrderStatus OrderStatus { get; private set; }
}

public class OrderStatus
{
    public static OrderStatus Submitted = new OrderStatus("Submitted");
    public static OrderStatus Cancelled = new OrderStatus("Cancelled");

    private readonly string _name;

    public OrderStatus(string name) =>_name = name;

    public override string ToString() => _name;

    public static IEnumerable<OrderStatus> GetAll() => new[] { Submitted, Cancelled };
}

public class OrderingContext : DbContext
{
    public DbSet<Order> Orders { get; set; }

    public OrderingContext(DbContextOptions<OrderingContext> options) : base(options) { }
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder
            .Entity<Order>()
            .Property(e => e.OrderStatus)
            .HasConversion(
                v => v.ToString(), 
                v => OrderStatus.GetAll().Single(s => s.ToString() == v));
    }
}

Note that on looking at the linked code for Enumeration I saw several issues:

  • It uses an unstable Equals (depends on a mutable property) which is likely to cause issues in identity resolution for EF.
  • It overrides Equals but not GetHashCode, which will cause issues with things like dictionary storage
  • The IComparable implementation is inconsistent with the Equals implementation

Also, EF would not be able to use the constructor defined on Order because other entity types cannot be used as constructor parameters as of EF Core 2.1.

@gojanpaolo
Copy link
Author

@ajcvickers Thanks for the response. I just tried the value converter approach but we would prefer to keep having an OrderStatus table with all its possible values and then use a surrogate key to relate it with other entities.

Another thing to consider is when a read-only entity class has other properties aside from a string name which should be reflected in the database.

@ajcvickers
Copy link
Member

@gojanpaolo In that case, what you are asking for is being tracked by #12206.

@gojanpaolo
Copy link
Author

Ah yes. I guess pre-loading the entities would cover those instances.
Thank you! :)

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants