The entity type 'Uri' requires a primary key to be defined

痞子三分冷 提交于 2021-02-05 06:37:27

问题


I reference a common model, so I don't have control over the attributes in it. Assuming it looks like this:

  public class Message
  {
      public Guid Id { get; set; }
      public string Sender { get; set; }
      public Uri Uri { get; set; }
  }

Where Uri is System.Uri.

In my context I then override OnModelCreating in order to set the primary key:

  public DbSet<Message> Messages { get; set; }

  protected override void OnModelCreating(ModelBuilder modelBuilder)
  {
      modelBuilder.Entity<Message>()
          .HasKey(i => i.Id);
  }

I then run Initialize in my database initializer:

  public void Initialize()
  {
      _logger.Information("Ensuring database is created.");

      _messageContext.Database.Migrate();
  }

But get the following error:

The entity type 'Uri' requires a primary key to be defined.

Any hints to how I can get around this?

EDIT:

Apparently this is a known issue.


回答1:


Entities can only contain two sorts of property types (that can be used by EF):

  1. Types that map to primitive SQL types (string, int, bool, ...).
  2. Types of entities defined in EF.

Since Uri does not map to a primitive SQL type, EF tries to handle it as if it were an entity.

Entities are stored in a separate table. To store an entity in a table, it needs a unique id.


There are ways around this. The method I'm suggesting maintains the Uri but prevents EF from trying to use it.

Step 1 - Make the Uri [NotMapped]

This makes it so that EF ignores the Uri property, since it can't properly handle it.

[NotMapped]
public Uri Uri { get; set; }

Step 2 - Make a string property that EF should handle instead of the Uri.

The string value is derived from the (now hidden) Uri property, but EF doesn't see that. It only sees a usable string property.

public String URL 
{
    get
    {
        return this.Uri.AbsoluteUri;
    }
    set
    {
        this.Uri = new Uri(value);
    }
 }

And that should do the trick. In code, you work with the Uri property directly (like you were already doing), but you've set up your entity so EF works with the URL string property instead.
After EF retrieves the entity and sets its properties (including the URL string property), the Uri property is implicitly also created and you can keep working with it.




回答2:


For EF Core 2.0 and older, @Flater's answer is correct.

For EF Core 2.1 there's more elegant and generic ways to solve that w/o polluting the model with conversion code: Value Converters.

In your case:

public class UriConverter : ValueConverter<Uri, string>
{
    public static UriConverter Instance = new UriConverter();

    private UriConverter() : base(value => ToUriString(value), value => ToUri(value)) { }

    private static string ToUriString (Uri uri) => uri?.ToString();

    private static Uri ToUri(string url) => Uri.TryPrase(url, out Uri uri)?uri:null;
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Message>().Property(e => e.Uri)
        .HasConversion(UriConverter.Instance);
}

Or for this very simple case w/o a helper class:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Message>().Property(e => e.Uri)
        .HasConversion(
            uri => uri?.ToString(),
            url => Uri.TryPrase(url, out Uri uri)?uri:null
        );
}


来源:https://stackoverflow.com/questions/49508954/the-entity-type-uri-requires-a-primary-key-to-be-defined

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!