简体   繁体   中英

Entity Framework Core Code First trying to add stored procedure as table

Hello I have a stored procedure called dbo.GetFolderDocumentsHierarchy and it maps directly to the following class.

public class DocumentDto:IModel
{
    public int FolderId { get; set; }
    [CanBeNull]
    public int Id { get; set; }
    public string FolderName { get; set; }
    public string DocumentName { get; set; }
    public string ParentName { get; set; }
    public int? ParentId { get; set; }
}

I have added it to my Context like so.

builder.Query<DocumentDto>();

I might add that I call it like:

var dtos = await _context
                .Set<DocumentDto>()
                .FromSqlRaw("EXEC dbo.GetFolderDocumentsHierarchy @Skip, @Take, @UserId", sqlParameters)
                .ToListAsync();

Now on to my issue, every time I create a new migration it adds the following code to the migration and to the snapshot.

            migrationBuilder.CreateTable(
            name: "DocumentDto",
            columns: table => new
            {
                FolderId = table.Column<int>(nullable: false),
                Id = table.Column<int>(nullable: false),
                FolderName = table.Column<string>(nullable: true),
                DocumentName = table.Column<string>(nullable: true),
                ParentName = table.Column<string>(nullable: true),
                ParentId = table.Column<int>(nullable: true)
            },
            constraints: table =>
            {
            });

And in the snapshot:

            modelBuilder.Entity("Models.Dtos.DocumentDto", b =>
            {
                b.Property<string>("DocumentName")
                    .HasColumnType("nvarchar(max)");

                b.Property<int>("FolderId")
                    .HasColumnType("int");

                b.Property<string>("FolderName")
                    .HasColumnType("nvarchar(max)");

                b.Property<int>("Id")
                    .HasColumnType("int");

                b.Property<int?>("ParentId")
                    .HasColumnType("int");

                b.Property<string>("ParentName")
                    .HasColumnType("nvarchar(max)");

                b.ToTable("DocumentDto");
            });

How can I make this not add to the migrations anymore?

EDIT: Adding full context.

    public virtual DbSet<ApplicationUser> ApplicationUsers { get; set; }
    public virtual DbSet<Appointment> Appointments { get; set; }
    public virtual DbSet<Campaign> Campaigns { get; set; }
    public virtual DbSet<Contact> Contacts { get; set; }
    public virtual DbSet<Customer> Customers { get; set; }
    public virtual DbSet<Deal> Deals { get; set; }
    public virtual DbSet<Document> Documents { get; set; }
    public virtual DbSet<Folder> Folders { get; set; }
    public virtual DbSet<Group> Groups { get; set; }
    public virtual DbSet<GroupDocument> GroupDocuments { get; set; }
    public virtual DbSet<UserDocument> UserDocuments { get; set; }
    public virtual DbSet<UserGroup> UserGroups { get; set; }
    public virtual DbSet<UserFolder> UserFolders { get; set; }
    public virtual DbSet<UserTask> UserTasks { get; set; }
    public virtual DbSet<Opportunity> Opportunities { get; set; }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);
        builder.Entity<UserTask>()
            .HasIndex(x => x.OwnerId);

        builder.Entity<Group>()
            .HasMany(x => x.Campaigns)
            .WithOne(x => x.Group);

#pragma warning disable 618 builder.Query(); #pragma warning restore 618

        builder.Entity<GroupDocument>()
            .HasKey(gd => new { gd.GroupId, gd.DocumentId });
        builder.Entity<UserDocument>()
            .HasKey(x => new {x.UserId, x.DocumentId});
        builder.Entity<UserFolder>()
            .HasKey(x => new {x.UserId, x.FolderId});

    }

The main trick is mentioned in Ivan Stoev's answer here , which is to add a ToView mapping statement, always. Even when there is no view. The only difference is that EF core 5 doesn't support .ToView(null) , you have to enter some string.

In general, your options are:

1

modelBuilder.Entity<DocumentDto>
( eb => 
    {
        eb.HasNoKey();
        eb.ToSqlQuery("EXEC StoredProcedureWithoutParameters");
        // Or: eb.ToSqlQuery("SELECT ... FROM ... ");
        eb.ToView("dummy view name"); // To prevent table creation.
    }
);

Or, when there is a view you map the DTO to:

2

modelBuilder.Entity<DocumentDto>
( eb => 
    {
        eb.HasNoKey();
        eb.ToView("MyView"); // Map to existing view
    }
);

Or, just to map the DTO as keyless entity:

3

modelBuilder.Entity<DocumentDto>
( eb => 
    {
        eb.HasNoKey();
        eb.ToView("Dummy view name"); // To prevent table creation
    }
);

When using option 3, you should populate the DTOs by code that you already have, something like:

context.Set<DocumentDto>()
       .FromSqlRaw("EXEC dbo.GetFolderDocumentsHierarchy @Skip, @Take, @UserId",
            sqlParameters)

This is the only way, to my knowledge, to use a stored procedure with parameters. Maybe there is some fancy way to use option 1 and make EF pass the predicates of a Where statement to the raw SQL query, but I doubt it.

I think the best option is to map the DTO to a real view (option 2, which you do now, as you mention in a comment), because in that case the IQueryable can be filtered by a Where statement. Also, it is composable in a way that generates one SQL statement, which allows the database engines query optimizer to calculate an overall query plan. Of course, this is only possible if the stored-procedure code can be transformed to a view.

You should be able to ignore the class inside OnModelCreating like so:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Ignore<DocumentDto>();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM