Can these models be represented in EF7?

c# entity-framework-core

Question

I'm trying to use some classes from another assembly in my own project as entities that I can persist using EF7, rather than writing a series of very similar classes that are more database-friendly.

Simplified versions look like this:

interface IMediaFile
{
    string Uri { get; }
    string Title { get; set; }
}
class CMediaFile : IMediaFile
{
    public CMediaFile() { }
    public string Uri { get; set; }
    public string Title { get; set; }
}

//The following types are in my project and have full control over.
interface IPlaylistEntry
{
    IMediaFile MediaFile { get; }
}
class CPlaylistEntry<T> : IPlaylistEntry where T : IMediaFile
{
    public CPlaylistEntry() { }
    public T MediaFile { get; set; }
}

There are multiple implementations of IMediaFile, I am showing only one. My PlaylistEntry class takes a generic argument to enable different traits for those various implementations, and I just work with the IPlaylistEntry.

So I've started to model it like so:

var mediaFile = _modelBuilder.Entity<CMediaFile>();
mediaFile.Key(e => e.Uri);
mediaFile.Index(e => e.Uri);
mediaFile.Property(e => e.Title).MaxLength(256).Required();

var mediaFilePlaylistEntry = _modelBuilder.Entity<CPlaylistEntry<CMediaFile>>();
mediaFilePlaylistEntry.Key(e => e.MediaFile);
mediaFilePlaylistEntry.Reference(e => e.MediaFile).InverseReference();

As a simple test, I ignore the CPlaylistEntry<> and just do:

dbContext.Set<CMediaFile>().Add(new CMediaFile() { Uri = "irrelevant", Title = "" });
dbContext.SaveChanges()

This throws:

NotSupportedException: The 'MediaFile' on entity type 'CPlaylistEntry' does not have a value set and no value generator is available for properties of type 'CMediaFile'. Either set a value for the property before adding the entity or configure a value generator for properties of type 'CMediaFile'`

I don't even understand this exception, and I don't see why CPlaylistEntry is appearing when I'm only trying to store a CMediaFile entity. I'm guessing this is related to my model definition - specifically defining the primary key of the CPlaylistEntry as not a simple type, but a complex type - another entity. However I would expect EF to be smart enough to work out that it all boils down to a string Uri, because that complex type has its own primary key declared already, and I have declared the property as a foreign key to that type.

Is it possible to model these classes in EF without radically redesigning them to look closer to what corresponding database tables might be? I've worked with EF6 database-first in the past, so this is my first attempt into a code-first pattern, and I'm really hoping that I can isolate the mess that a database might look like to just my model definition, and keep "clean" classes that I interact with in .NET.

If more explanation of these types and their relationship is required, just ask - I'm attempting to keep this brief.

Accepted Answer

Doubt this is currently supported (unsure if it eventually will or not).| I've tried to recreate your model with slight changes and when trying to create the database I get:

System.NotSupportedException: The property 'PlaylistEntry`1MediaFile' cannot be mapped because it is of type 'MediaFile' which is currently not supported.

Update 1

I think that the fact that you are putting MediaFile as a key is creating problems. I've done a few changes to your model. I hope this will not break anything negative on your end:

public interface IPlaylistEntry<T>
    where T : IMediaFile
{
    T MediaFile { get; set; }
}

public class PlaylistEntry<T> : IPlaylistEntry<T>
    where T : IMediaFile
{
    public int Id { get; set; }
    public string PlaylistInfo { get; set; } //added for testing purposes
    public T MediaFile { get; set; }
}

Mappings:

protected override void OnModelCreating(ModelBuilder builder)
  {
     builder.ForSqlServer().UseIdentity();

     builder.Entity<MediaFile>().ForRelational().Table("MediaFiles");
     builder.Entity<MediaFile>().Key(e => e.Uri);
     builder.Entity<MediaFile>().Index(e => e.Uri);
     builder.Entity<MediaFile>().Property(e => e.Title).MaxLength(256).Required();

     builder.Entity<PlaylistEntry<MediaFile>>().ForRelational().Table("MediaFileEntries");
     builder.Entity<PlaylistEntry<MediaFile>>().Key(e => e.Id);
     builder.Entity<PlaylistEntry<MediaFile>>().Reference(e => e.MediaFile).InverseReference();
 }

Usage:

  var mediaFile = new MediaFile() {Uri = "irrelevant", Title = ""};
  context.Set<MediaFile>().Add(mediaFile);
  context.SaveChanges();

  context.Set<PlaylistEntry<MediaFile>>().Add(new PlaylistEntry<MediaFile>
  {
      MediaFile = mediaFile,
      PlaylistInfo = "test"
  });

  context.SaveChanges();

This works and saves the correct data to the database.

You can retrieve the data using:

  var playlistEntryFromDb = context.Set<PlaylistEntry<MediaFile>>()
         .Include(plemf => plemf.MediaFile).ToList();

Update 2

Since you do not want to have an identity as key, you can add a Uri property to your playlistentry class that will be used for the relationship between PlaylistEntry and MediaFile.

public class PlaylistEntry<T> : IPlaylistEntry<T>
    where T : IMediaFile
{
    public string Uri { get; set; }
    public string PlaylistInfo { get; set; }
    public T MediaFile { get; set; }
}

Here is what the mapping in this case would look like:

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<MediaFile>().ForRelational().Table("MediaFiles");
        builder.Entity<MediaFile>().Key(e => e.Uri);
        builder.Entity<MediaFile>().Index(e => e.Uri);
        builder.Entity<MediaFile>().Property(e => e.Title).MaxLength(256).Required();

        builder.Entity<PlaylistEntry<MediaFile>>().ForRelational().Table("MediaFileEntries");
        builder.Entity<PlaylistEntry<MediaFile>>().Key(e => e.Uri);
        builder.Entity<PlaylistEntry<MediaFile>>().Reference(e => e.MediaFile).InverseReference().ForeignKey<PlaylistEntry<MediaFile>>(e => e.Uri);
    }

Usage to insert data stays the same:

 var mediaFile = new MediaFile() { Uri = "irrelevant", Title = "" };
 context.Set<MediaFile>().Add(mediaFile);
 context.SaveChanges();

 context.Set<PlaylistEntry<MediaFile>>().Add(new PlaylistEntry<MediaFile>
 {
     MediaFile = mediaFile,
     PlaylistInfo = "test"
 });

 context.SaveChanges();

This code above will put "irrelevant" in the PlaylistEntry Uri property since it is used as the foreign key.

And to retrieve data:

    var mediaFiles = context.Set<PlaylistEntry<MediaFile>>().Include(x => x.MediaFile).ToList();

The join will occur on the Uri field in both tables.



Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why