c# - insertar - relacion muchos a muchos tabla intermedia
NHibernate: muchas a muchas consultas utilizando Junction/Joiner Table (2)
He encontrado preguntas muy similares aquí, pero ninguna que coincida exactamente con lo que estoy buscando. Los dos hilos más cercanos que he encontrado son (sí, son hilos diferentes):
Criterios de muchos a muchos de NHibernate (1)
Criterios de muchos a muchos de NHibernate (2)
Sin embargo, creo que ambos usan relaciones directas de Muchos a Muchos. En realidad estoy simulando la relación de Muchos a Muchos al tener dos relaciones de Uno a Muchos con una tabla de unión, que es una práctica bastante común. Aquí están mis asignaciones NHibernate:
Archivos:
<class name="Files" table="files">
<id name="id">
<generator class="identity" />
</id>
<property name="name" />
<bag name="files_attrs" table="files_attrs" lazy="true">
<key column="file_id" />
<one-to-many class="Files_Attrs" />
</bag>
</class>
Atributos:
<class name="Attrs" table="attrs">
<id name="id">
<generator class="identity" />
</id>
<property name="name" />
<property name="value" />
<bag name="files_attrs" table="files_attrs" lazy="true">
<key column="attr_id" />
<one-to-many class="Files_Attrs" />
</bag>
</class>
Carpintero:
<class name="Files_Attrs" table="files_attrs">
<id name ="id">
<generator class="identity" />
</id>
<many-to-one name="file" cascade="all" column="file_id" />
<many-to-one name="attr" cascade="all" column="attr_id" />
</class>
Así que mi problema es exactamente como el segundo enlace anterior, pero hecho con una tabla de unión. Asi que:
Dado un conjunto de ID de atributos, espero ejecutar una consulta que me proporcione los archivos que tienen TODOS esos atributos coincidentes. Puedo ejecutar fácilmente "n" consultas para cada ID de atributo en el conjunto y comparar cada lista de ID de archivo que aparecen en cada lista, pero creo que debería haber una manera más fácil de hacerlo todo de una vez con una sola consulta.
Ejemplo:
File | Attributes
----------+-----------------------------------------------------
foo.txt | (mode = read-only, view = visible)
bar.txt | (mode = read-write, security = all, view = visible)
duck.txt | (mode = read-only, view = hidden)
goose.txt | (more = read-only, security = owner, view = visible)
Dados estos atributos: mode = read-only
y view = visible
, deseo que me devuelvan solo foo.txt
y goose.txt
.
Puede alguien ayudarme con esto? Gracias.
No estoy seguro si esto es lo que necesitas:
<bag name="files_attrs" table="files_attrs" lazy="true" where="something like ''%mode = read-only%'' and something like ''%view = visible%''">
<key column="attr_id" />
<one-to-many class="Files_Attrs" />
</bag>
Donde something
es el atributo o es la columna donde se filtran los datos.
Prueba esta consulta:
Files fAlias = null;
Attrs aAlias = null;
var disjunction = new Disjunction();
disjunction.Add(Restrictions.On(() => aAlias.value)
.IsLike("mode = read-only", MatchMode.Anywhere));
disjunction.Add(Restrictions.On(() => aAlias.value)
.IsLike("view = visible", MatchMode.Anywhere));
var subquery = QueryOver.Of<Files_Attrs>
.Inner.JoinAlias(x => x.file, () => fAlias)
.Inner.JoinAlias(x => x.attr, () => aAlias)
.Where(disjunction)
.Select(() => fAlias);
var files = session.QueryOver<Files>
.WithSubquery.WhereExists(subquery)
.List();
Una forma de lograr esto podría ser crear tantas subconsultas unidas por AND, ya que se deben encontrar / relacionar muchos atributos con los archivos buscados.
Estoy buscando nombre / valor
La primera solución funciona con los pares nombre / valor, desde la capa superior. Es decir, que el modo seleccionado por el usuario sea de solo lectura ... (el segundo será un poco más fácil, esperando que ya tengamos identificaciones de los Atributos buscados)
// Below I am using C# properties, which I guess are correct
// based on the mapping. Naming convention is more Java (camel)
// but this should work with above mapping
// (also - class name Contact, not File)
Files file = null; // this is an alias used below
// here the attributes collection represents search filter
// ... settings for which is user looking for
var attributes = new List<Attrs>
{
new Attrs{ name = "mode", value = "read-only" },
new Attrs{ name = "view", value = "visible" }
};
// Let''s start with definition of the outer/top query
// which will return all files, which do meet all filter requirements
var query = session.QueryOver<Files>(() => file);
En el siguiente paso, iteraremos a través de los atributos, es decir, la recolección de filtros
// here we will take each attribute and create a subquery
// all these subqueries, will be joined with AND
// so only these files, which do have all attributes, will be selected
foreach (var attr in attributes)
{
// create the subquery, returning the FileId
Attrs attribute = null;
var subQueryForAttribute = QueryOver.Of<Files_Attrs>()
.JoinQueryOver(fa => fa.attr, () => attribute)
.Select(x => x.file.id)
;
// now, take name and value
var name = attr.name;
var value = attr.value;
// and convert them into where condition
subQueryForAttribute.Where(() => attribute.name == name);
subQueryForAttribute.Where(() => attribute.value == value);
// finally, add this subquery as a restriction to the top level query
query.WithSubquery
.WhereProperty(() => file.id)
.In(subQueryForAttribute);
}
Ahora tenemos una consulta, que está lista para admitir paginación, porque estamos trabajando en una estructura plana de archivos. Entonces podemos usar Tomar y omitir si es necesario y luego obtener la lista de archivos buscados
// query.Take(25);
// query.Skip(100);
var list = query.List<Files>();
Esta es una consulta que dará como resultado un SELECT como este
SELECT ...
FROM files
WHERE id IN (SELECT file_Id FROM files_attrs
INNER JOIN attrs ON attrs.id = file_attrs.attr_id
WHERE name = ''mode'' AND value = ''read-only'' )
AND id IN (SELECT file_Id FROM files_attrs
INNER JOIN attrs ON attrs.id = file_attrs.attr_id
WHERE name = ''view'' AND value = ''visible'' )
II buscando por atributos ID
La segunda solución, tiene condiciones de inicio más fáciles, en lugar de atributos (nombre y valor) ya tenemos sus Id. (Cite de una pregunta :)
Dado un conjunto de ID de atributos, espero ejecutar una consulta que me proporcione los archivos que tienen TODOS esos atributos coincidentes.
// Below I am using C# properties, which I guess are correct
// based on the mapping. Naming convention is more Java (camel)
// but this should work with above mapping
// (also - class name Files, not File)
Files file = null; // this is an alias used below
// here the attributeIds collection represents attributes to be found
var attributeIds = new List<int> { 1, 4, 5 };
// Let''s again start with definition of the outer/top query
// which will return all files, which do meet all filter requirements
var query = session.QueryOver<Files>(() => file);
A continuación está la iteración a través del conjunto de ID conocidos que deben existir como relación (todos ellos)
// here we will take each attribute and create a subquery
// all these subqueries, will be joined with AND
// so only these files, which do have all attributes, will be selected
foreach (var attrId in attributeIds)
{
// create the subquery, returning the Files.id
var subQueryForAttribute = QueryOver.Of<Files_Attrs>()
// no need to join, all the stuff is in the pairing table
.Select(x => x.file.id)
;
var id = attrId; // local variable
// and convert them into where condition
subQueryForAttribute.Where(pair => pair.attr.id == id);
// finally, add this subquery as a restriction to the top level query
query.WithSubquery
.WhereProperty(() => file.id)
.In(subQueryForAttribute);
}
var list = query.List<Files>();
La solución con IDS conocido es un poco más fácil (se necesitan menos tablas en SQL statemenets)
NOTA: tengo que decir: es genial ver que has introducido el many-to-one
y one-to-many
lugar de muchos-a-muchos. Personalmente, diría que exactamente este ejemplo muestra, qué gran beneficio podría aportar ... la capacidad de buscar incluso con filtros complejos
Algunos enlaces, para mostrar el poder de QueryOver
: consultar en la referencia de HasMany , y algunas buenas razones para no usar el mapeo de muchos a muchos : muchos a muchos con columnas adicionales nhibernate