How to support columns added by the user without updating the EF model?

7

I have a C# application with a Entity Framework 6 "Database First" model created from the base structure of a SQL Server database, which belongs to third-party software.

Users of this software can add user fields to some tables by defining their name and data type, which are added to the table in SQL Server.

Example: In an implementation, the client can add the Products table an u_cor field of type nvarchar, in another implementation the client can add to the table a u_dataPrimaryPurchase column of type datetime. >

This Products table will thus have a set of columns present in all software implementations, but in each implementation specific columns of that implementation can be added.

My question is: Is there a way, at runtime, to tell the model of the existence of additional columns, for example: defining them in a .config , so that you can assign or retrieve values from these additional columns, or otherwise "direct" assign values to those columns, even without mirroring them in the template?     

asked by anonymous 15.04.2014 / 16:39

1 answer

4

I think the Entity Framework will not care about the additional columns, as long as they do not hinder the salvage of entities without additional values.

Adding columns without disrupting Entity

To do this simply set a default value for the additional fields, creating them with the same pure SQL.

You would have to store which additional columns there are. It may even be in the same database, let's say in a table called ExtraColumns , which could be read by the Entity Framework and normally mapped to a ExtraColumn class.

using (var context = new MyContext())
{
    // pegando o ObjectContext
    var objectContext = context is DbContext
         ? ((IObjectContextAdapter)context).ObjectContext
         : context as ObjectContext;

    var isNullable = false;
    var nomeTabela = "Produtos";
    var nomeColuna = "NovaColuna"; // NOTA: se essa string vier do usuário então será
                                   // necessário validar o texto para evitar SQL Injection
    // Exemplo: permitir apenas letras
    if (nomeColuna.Any(ch => !char.IsLetter(ch)))
        throw new Exception("Senhor usuário, o nome da coluna permite apenas letras.");

    var tipoColuna = "nvarchar(max)";

    objectContext.ExecuteStoreCommand(string.Format(@"
        ALTER TABLE {0} 
        ADD [{1}] {2} {3} 
        CONSTRAINT DEFAULT_{0}_{1} DEFAULT {4}
    ", nomeTabela, nomeColuna, tipoColuna, isNullable ? "NULL" : "NOT NULL", valorDefault));

    var extraColumn = new ExtraColumn
    {
        NomeTabela = nomeTabela,
        NomeColuna = nomeColuna,
        TipoColuna = tipoColuna,
    }
    context.ExtraColumns.Add(extraColumn);
    context.SaveChanges();
}

Retrieving values from extra columns

To retrieve values in these additional fields, you can use the DbContext or ObjectContext , but without going through the ORM itself, using the connection itself with the database and creating a DbCommand and then a DbDataReader :

using (var context = new MyContext())
{
    // pegando o ObjectContext
    var objectContext = context is DbContext
         ? ((IObjectContextAdapter)context).ObjectContext
         : context as ObjectContext;

    var nomeTabela = "Produtos";

    var extraColumns = context.ExtraColumns
        .Where(c => c.NomeTabela == nomeTabela)
        .ToList();

    var query = "SELECT "
        + string.Join(",", extraColumns.Select(c => c.NomeColuna))
        + " FROM " + nomeTabela;

    // pegando a conexão com o DB a partir do ObjectContext
    var conn = ((EntityConnection)objectContext.Connection).StoreConnection;

    using (var command = conn.CreateCommand())
    {
        command.CommandText = query;
        var reader = command.ExecuteReader();
        while (reader.Read())
        {
            // reader[0] agora contém a primeira coluna extra
            // reader[1] agora contém a segunda coluna extra
            // ...
        }
    }
}

Saving values from extra columns

To save values in these additional fields, you can use the ExecuteStoreCommand method, as we did before to create the new columns:

using (var context = new MyContext())
{
    // pegando o ObjectContext
    var objectContext = context is DbContext
         ? ((IObjectContextAdapter)context).ObjectContext
         : context as ObjectContext;

    var nomeTabela = "Produtos";
    var values = __lista_de_valores_a_atualizar__;

    var extraColumns = context.ExtraColumns
        .Where(c => c.NomeTabela == nomeTabela)
        .ToList();

    var queryFmt = "UPDATE "
        + string.Join(",", extraColumns.Select((c, i) => string.Format("{0} = {{{1}}}", c.NomeColuna, i))
        + " FROM " + nomeTabela;

    // NOTA: O método abaixo NÃO é suscetível a SQL Injection
    objectContext.ExecuteStoreCommand(queryFmt, values.ToArray());
}
    
15.04.2014 / 16:52