2010-11-03 15 views
5

He encontrado algo de información sobre esto, pero no lo suficiente como para entender cuál es la mejor práctica para este escenario. Tengo su configuración típica de TPH con una clase base abstracta "Firme". Tengo varios hijos, "Pequeña empresa", "Gran empresa", etc. que heredan de la empresa. En realidad, en realidad tengo diferentes clasificaciones realistas para las empresas, pero estoy tratando de mantenerlo simple en este ejemplo. En la base de datos según TPH tengo una única tabla Firm con una columna FirmTypeId (int) que diferencia entre todos estos tipos. Todo funciona muy bien, excepto que tengo un requisito para permitir que un usuario cambie un tipo de empresa en otra. Por ejemplo, un usuario podría haber cometido un error al agregar la empresa y le gustaría cambiarlo de Gran empresa a Pequeña empresa. Debido a que el marco de la entidad no permite exponer la columna discriminatoria de la base de datos como una propiedad, no creo que haya una forma de cambiar un tipo a otro a través de EF. Por favor, corríjame si estoy equivocado. Como yo lo veo, tengo dos opciones:Entity Framework 4 TPH inheritance, ¿cómo cambiar un tipo a otro?

  1. No utilice TPH. Simplemente tenga una entidad firme y vuelva a usar .Where (FirmTypeId == something) para diferenciar entre los tipos.
  2. Ejecute SQL directamente utilizando context.ExecuteStoreCommand para actualizar la columna FirmTypeId de la base de datos.

He visto una publicación donde la gente sugiere que Uno de los principios de OOP es que las instancias no pueden cambiar su tipo. Aunque tiene mucho sentido para mí, parece que no puedo conectar los puntos. Si tuviéramos que seguir esta regla, entonces el único momento para usar cualquier tipo de herencia (TPH/TPT) es cuando uno está seguro de que un tipo nunca se convertiría en otro. Entonces, una pequeña empresa nunca se convertirá en una gran empresa. Veo sugerencias de que la composición debería usarse en su lugar. A pesar de que no tiene sentido para mí (lo que significa que no veo cómo una empresa tiene una gran empresa, para mí una gran empresa es una empresa), puedo ver cómo se puede modelar la composición en EF si los datos están en múltiples tablas Sin embargo, en una situación en la que tengo una sola tabla en la base de datos, parece que es TPH o lo que he descrito en los números 1 y 2 anteriores.

+0

Ese "alguien" puede haber sido yo. En OO basado en clases, los objetos no pueden cambiar tipos. Nunca. C# no permite esto. El EF ciertamente no agrega tal característica a C#. Lo único que es diferente en el EF es que la vida del objeto es más larga, efectivamente, para siempre. –

+0

Craig, podría haber sido usted. Acabo de editar la publicación para incluir un poco más de información sobre esto también. Si no te importa revisar el último párrafo en mi publicación y arrojar algo de luz sobre el enfoque correcto. – e36M3

+0

¿Cuál es la diferencia entre una "pequeña empresa" y una "gran empresa", exactamente? Por cierto, no recibo notificaciones de tus respuestas a menos que pongas @Craig en ellas. –

Respuesta

1

Sí, lo tienes todo bien. La herencia de EF no es compatible con este escenario. La mejor manera de cambiar un tipo Empresa para una empresa existente es utilizar un procedimiento almacenado.

favor, eche un vistazo a este post para más información:
Changing Inherited Types in Entity Framework

+1

Estas son las conclusiones a las que he llegado. En este punto, sin embargo, no estoy seguro de que sea la forma correcta de resolver este problema, parece que si uno tiene este problema que abordaron el problema incorrectamente para empezar (según Craig) – e36M3

0

menos que explícitamente desee utilizar la funcionalidad polimórfica de la herencia relacional, entonces por qué no mirar a una estrategia de división?

http://msdn.microsoft.com/en-us/data/ff657841.aspx

+0

no estoy seguro de cómo esto realmente me ayuda . En mi situación, hay una columna Tipo que discrimina si una entidad es de tipo A, B o C. Realmente no tienen atributos propios. Huele a herencia, porque posiblemente podría agregar algunas propiedades más a cada uno de estos diferentes tipos para describir mejor los detalles de sus tipos. Sin embargo, parece que la herencia no se debe usar cuando existan requisitos comerciales que permitan cambiar un tipo a otro. – e36M3

+0

Bueno, puedes dividir el tipo en una bandera usando uno de estos métodos, luego al cambiar la bandera, esencialmente cambias el tipo. Y como digo, la herencia solo es obligatoria si quieres usar el polimorfismo. Entonces, en esencia, tiene 2 tipos diferentes con propiedades similares, que difieren solo en una o dos propiedades. – Slappy

1

he topó con este problema en nuestro proyecto, en el que tenemos núcleo DBContext y algunos módulos "enchufables" con su propio DBContexts, en la que "el usuario del módulo" hereda "(base) de usuario básico" . Espero que sea comprensible.

También se necesita la capacidad de cambiar (vamos a llamarlo) User a Customer (y si es necesario también a otro "heredada" Users al mismo tiempo, por lo que el usuario puede utilizar todos los módulos.

Debido que intentamos usar la herencia TPT, en lugar de TPH, pero TPH funcionaría de alguna manera también.

Una forma es utilizar procedimiento almacenado personalizado como lo sugieren muchas personas ...

Otra forma en la que me vino a la mente es enviar solicitud personalizada de inserción/actualización a la base de datos. En TPT sería:

private static bool UserToCustomer(User u, Customer c) 
    { 
     try 
     { 
      string sqlcommand = "INSERT INTO [dbo].[Customers] ([Id], [Email]) VALUES (" + u.Id + ", '" + c.Email + "')"; 
      var sqlconn = new SqlConnection(ConfigurationManager.ConnectionStrings["DBContext"].ConnectionString); 
      sqlconn.Open(); 
      var sql = new SqlCommand(sqlcommand, sqlconn); 
      var rows = sql.ExecuteNonQuery(); 
      sqlconn.Close(); 

      return rows == 1; 
     } 
     catch (Exception) 
     { 
      return false; 
     } 
    } 

En este escenario Customer hereda User y sólo tiene string Email.

Al usar TPH la consulta solo cambiaría de INSERT ... VALUES ... a UPDATE ... SET ... WHERE [Id] = .... No olvide cambiar la columna Discriminator también.

Después de la siguiente llamada dbcontext.Users.OfType<Customer> está nuestro usuario original, "convertido" al cliente.


En resumen: Yo también trató la solución de otra pregunta aquí, que incluía separar entidad original (usuario) de ObjectStateManager y haciendo nueva entidad (cliente) estado modificado, a continuación, guardar dbcontext.SaveChanges(). Eso no funcionó para mí (ni TPH ni TPT). Ya sea porque usa DBContexts por módulo o porque EntityFramework 6 (.1) lo ignora. It can be found here.

+1

Al ver esto después de un tiempo, sería más limpio usar ** parámetros de SQL ** en la consulta en lugar de concatenar cadenas. – podvlada

0

EDIT: DISCULPAS, esta es una respuesta EF 6.x

estoy escribiendo código de ejemplo para la integridad. En este escenario, tengo una clase base Thing. Entonces, subclases: ActiveThing y DeletedThing

Mi OData ThingsController, tiene una principal GetThings que pretendo solamente para exponer ActiveThing s, pero, es GetThing (ThingId) todavía puede volver cualquier tipo de objeto. La acción Delete realiza una conversión de ActiveThing a DeletedThing en la forma solicitada por el OP, y de la manera descrita en otras respuestas. Estoy usando SQL en línea (parametrizado)

public class myDbModel:DbContext 
{ 
    public myDbModel(): base("name=ThingDb"){} 

    public DbSet<Thing> Things { get; set; } //db table 

    public DbSet<ActiveThing> ActiveThings { get; set; } // now my ThingsController 'GetThings' pulls from this 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     //TPH (table-per-hierarchy): 
     modelBuilder.Entity<Ross.Biz.ThingStatusLocation.Thing>() 
     .Map<Ross.Biz.ThingStatusLocation.ActiveThing>(thg => thg.Requires("Discriminator").HasValue("A")) 
     .Map<Ross.Biz.ThingStatusLocation.DeletedThing>(thg => thg.Requires("Discriminator").HasValue("D")); 
    } 

} 

Aquí está mi ThingsController.cs actualizados

public class ThingsController : ODataController 
{ 
    private myDbModel db = new myDbModel(); 

    /// <summary> 
    /// Only exposes ActiveThings (not DeletedThings) 
    /// </summary> 
    /// <returns></returns> 
    [EnableQuery] 
    public IQueryable<Thing> GetThings() 
    { 
     return db.ActiveThings; 
    } 

    public async Task<IHttpActionResult> Delete([FromODataUri] long key) 
    { 
     using (var context = new myDbModel()) 
     { 
      using (var transaction = context.Database.BeginTransaction()) 
      { 
       Thing thing = await db.Things.FindAsync(key); 
       if (thing == null || thing is DeletedThing) // love the simple expressiveness here 
       { 
        return NotFound();//was already deleted previously, so return NotFound status code 
       } 

       //soft delete: converts ActiveThing to DeletedThing via direct query to DB 
       context.Database.ExecuteSqlCommand(
        "UPDATE Things SET Discriminator='D', [email protected] WHERE [email protected]", 
        new SqlParameter("@ThingId", key), 
        new SqlParameter("@NowDate", DateTimeOffset.Now) 
        ); 

       context.ThingTransactionHistory.Add(new Ross.Biz.ThingStatusLocation.ThingTransactionHistory 
       { 
        ThingId = thing.Id, 
        TransactionTime = DateTimeOffset.Now, 
        TransactionCode = "DEL", 
        UpdateUser = User.Identity.Name, 
        UpdateValue = "MARKED DELETED" 
       }); 
       context.SaveChanges(); 
       transaction.Commit(); 
      } 
     } 

     return StatusCode(HttpStatusCode.NoContent); 
    } 
} 
Cuestiones relacionadas