4

Let's say I have a model like this:

public class Model {
    [Key]
    public int Id { get; set; }
    public string SmallData { get; set; }
    public byte[] VeryLargeBlob { get; set; }
}

I need to read an entity from the database and use only the field SmallData, and the VeryLargeBlob is not needed. VeryLargeBlob is very large (say, several megabytes) and reading it from database can affect performance.

I understand that I can choose some of the fields in this way:

var data = context.Model.Where(m => m.Id == Id).Select(m => new { Id = m.Id, SmallData = m.SmallData }).FirstOrDefault();

But I need a Model instance and not an anonymous object, so that I can use it for later queries. Even if I hack it and make it return a Model object the instance isn't tracked and won't be compatible with lazy loading.

Is there a way to load only partial data from the model?

vincent163
  • 384
  • 2
  • 13
  • 4
    take a look at this . [partial-load](https://stackoverflow.com/questions/12668469/entity-framework-partial-load) – Adam Sep 19 '18 at 13:32
  • 3
    If you can change database, you can move VeryLargeBlob into separate table and lazy-load that on demand. That would be simplest way to solve your problem. – dlxeon Sep 19 '18 at 13:32
  • In old dbml's, I know its not EF, I managed using database views. Could work also anonymous types – Cleptus Sep 19 '18 at 13:35
  • 1
    @dlxeon it would ease database administration for sure. – Cleptus Sep 19 '18 at 13:37

1 Answers1

10

Use "Table Splitting" to split a single table among two or more entity classes, allowing you to control if and when you load certain properties. One achieves this by specifying the same table name for multiple entities and using a shared primary key (PK of dependent entities are a FK to the principal entity). Here's an example:

public class PrincipalEntity
{
    [Key]
    public int Id { get; set; }
    public string PrincipalProperty { get; set; }
    public virtual DependentEntity Dependent { get; set; }
}

public class DependentEntity
{
    [Key]
    public int Id { get; set; }
    public string DependentProperty { get; set; }
}

public class PricipalEntityConfiguration : IEntityTypeConfiguration<PrincipalEntity>
{
    public void Configure( EntityTypeBuilder<PrincipalEntity> builder )
    {
        //builder.HasKey( pe => pe.Id );
        builder.HasOne( pe => pe.Dependent )
            .WithOne()
            .HasForeignKey<DependentEntity>( de => de.Id ); // FK is PK
        builder.ToTable( "YourTableName" );
    }
}

public class DependentEntityConfiguration : IEntityTypeConfiguration<DependentEntity>
{
    public void Configure( EntityTypeBuilder<DependentEntity> builder )
    {
        //builder.HasKey( de => de.Id );
        builder.ToTable( "YourTableName" ); // same table name
    }
}

public class TestContext : DbContext
{
    public DbSet<PrincipalEntity> PrincipalEntities { get; set; }
    public DbSet<DependentEntity> DependentEntities { get; set; }

    public TestContext( DbContextOptions options ) : base( options )
    {
    }

    protected override void OnModelCreating( ModelBuilder modelBuilder )
    {
        modelBuilder.ApplyConfiguration( new PricipalEntityConfiguration() );
        modelBuilder.ApplyConfiguration( new DependentEntityConfiguration() );
    }
}

class Program
{
    static void Main( string[] args )
    {
        var options = new DbContextOptionsBuilder<TestContext>()
            .UseSqlServer( "Server=(localdb)\\mssqllocaldb;Database=EFCoreTest;Trusted_Connection=True;" )
            .Options;

        using( var dbContext = new TestContext( options ) )
        {
            var pEntity = new PrincipalEntity()
            {
                PrincipalProperty = "Principal Property Value",
                Dependent = new DependentEntity()
                {
                    DependentProperty = "Dependent Property Value",
                },
            };

            dbContext.PrincipalEntities.Add( pEntity );
            dbContext.SaveChanges();
        }

        using( var dbContext = new TestContext( options ) )
        {
            var pEntity = dbContext.PrincipalEntities
                // eager load dependent
                .Include( pe => pe.Dependent )
                .Single();

            System.Console.WriteLine( "Loaded Principal w/ Dependent Eager-Loaded:" );
            DisplayValues( pEntity );

            dbContext.Entry( pEntity.Dependent ).State = EntityState.Detached;
            dbContext.Entry( pEntity ).State = EntityState.Detached;
            pEntity = dbContext.PrincipalEntities.Single();

            System.Console.WriteLine();
            System.Console.WriteLine( "Load Principal Entity Only:" );
            DisplayValues( pEntity );

            // explicitly load dependent
            dbContext.Entry( pEntity )
                .Reference( pe => pe.Dependent )
                .Load();

            System.Console.WriteLine();
            System.Console.WriteLine( "After Explicitly Loading Dependent:" );
            DisplayValues( pEntity );                
        }
    }

    private static void DisplayValues( PrincipalEntity pe )
    {
        System.Console.WriteLine( $"Principal Entity = {{ Id: {pe.Id}, PrincipalProperty: \"{pe.PrincipalProperty}\" }}" );

        if( null == pe.Dependent )
        {
            System.Console.WriteLine( "Principal Entity's Dependent property is null" );
        }
        else
        {
            System.Console.WriteLine( $"Dependent Entity = {{ Id: {pe.Dependent.Id}, DependentProperty: \"{pe.Dependent.DependentProperty}\" }}" );
        }
    }

Results:

enter image description here

Table in Database:

enter image description here

Moho
  • 15,457
  • 1
  • 30
  • 31
  • That's actually a great answer and I used it for the same use case as OP, a single property that is not part of the "header information" of the entity and can get quite big. More info here: https://learn.microsoft.com/en-us/ef/core/modeling/table-splitting – Timmos Apr 03 '23 at 07:59