c# - async - Entity Framework establece la propiedad de navegación en nulo
query entity framework c# (8)
A partir de Entity Framework 5.0:
db.Entry(project).Reference(p => p.ResponsibleUser).CurrentValue = null;
Tengo un primer proyecto de base de datos entidad marco. Aquí hay una extracción del modelo:
public partial class LedProject
{
public LedProject()
{
this.References = new HashSet<LedProjectReference>();
this.Results = new HashSet<LedProjectResult>();
this.History = new HashSet<LedProjectHistory>();
}
public string Identifier { get; set; }
public string Name { get; set; }
public Nullable<System.DateTime> CompletionDate { get; set; }
public System.DateTime CreationDate { get; set; }
public System.Guid ProjectId { get; set; }
public string Comment { get; set; }
public virtual User ContactUser { get; set; }
public virtual User CreationUser { get; set; }
public virtual Customer Customer { get; set; }
public virtual LedProjectAccounting Accounting { get; set; }
public virtual LedProjectState State { get; set; }
public virtual ICollection<LedProjectReference> References { get; set; }
public virtual ICollection<LedProjectResult> Results { get; set; }
public virtual User ResponsibleUser { get; set; }
public virtual ICollection<LedProjectHistory> History { get; set; }
}
public partial class User
{
public System.Guid UserId { get; set; }
public string LoginName { get; set; }
public System.DateTime CreationDate { get; set; }
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Email { get; set; }
}
Tengo un problema con la configuración del elemento de navegación ResponsibleUser
de la clase LedProject
. Cuando configuro el ResponsibleUser
a otro usuario y luego guardo los cambios del DBContext, los cambios se almacenan en la base de datos.
Pero, cuando quiero eliminar el ResponsibleUser
actual de un LedProject
, configurando la propiedad de navegación en nulo. Los cambios no se almacenan en la base de datos.
LedProject project = db.LedProject.Find(projectId);
project.Name = string.IsNullOrEmpty(name) ? null : name;
...
project.ResponsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
...
db.SaveChanges();
¿Hay algún truco para eliminar las propiedades de navegación?
Como dijo , el problema es con la carga perezosa. Sin embargo, solo tiene que cargar la propiedad cuando desee anularla para que la maquinaria de Entity Framework sepa que ha cambiado. El código podría verse como:
responsibleUser = responsibleUser == null ? null : db.User.Find(responsibleUser.UserId);
if (responsibleUser == null)
{
// load the value to assure setting it to null will cause a change
var dummy = project.ResponsibleUser;
}
project.ResponsibleUser = responsibleUser;
...
db.SaveChanges();
Sigo pensando que debería haber una forma de usar db.ChangeTracker para forzar un guardado sin carga, pero aún no lo he encontrado (y las pocas cosas que he intentado me parecieron realmente intrépidas).
Como otra solución, compilé dos métodos en un método de extensión:
public static void SetToNull<TEntity, TProperty>(this TEntity entity, Expression<Func<TEntity, TProperty>> navigationProperty, DbContext context = null)
where TEntity : class
where TProperty : class
{
var pi = GetPropertyInfo(entity, navigationProperty);
if (context != null)
{
//If DB Context is supplied, use Entry/Reference method to null out current value
context.Entry(entity).Reference(navigationProperty).CurrentValue = null;
}
else
{
//If no DB Context, then lazy load first
var prevValue = (TProperty)pi.GetValue(entity);
}
pi.SetValue(entity, null);
}
static PropertyInfo GetPropertyInfo<TSource, TProperty>( TSource source, Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);
MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression ''{0}'' refers to a method, not a property.",
propertyLambda.ToString()));
PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression ''{0}'' refers to a field, not a property.",
propertyLambda.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expression ''{0}'' refers to a property that is not from type {1}.",
propertyLambda.ToString(),
type));
return propInfo;
}
Esto le permite proporcionar un DbContext si tiene uno, en cuyo caso utilizará el método más eficiente y establecerá el valor actual de la referencia de entrada en nulo.
entity.SetToNull(e => e.ReferenceProperty, dbContext);
Si no se proporciona DBContext, primero se cargará perezosamente.
entity.SetToNull(e => e.ReferenceProperty);
Tenga en cuenta que este problema es esencialmente un duplicado de: Entity Framework solo establecerá la propiedad de la entidad relacionada en "null" si primero obtengo la propiedad y si configuro una clave foránea en null al usar primero el código del framework de la entidad
Consulte https://docs.microsoft.com/en-us/ef/ef6/fundamentals/relationships .
La sección Creación y modificación de relaciones explica lo que sucede con respecto a la propiedad de clave externa y la propiedad de navegación, tanto al asignar como a la opción nula.
Hay algunos cambios en EF5 y hacia adelante, pero la clave de ellos es definir la propiedad de clave externa para que la relación ya no sea una asociación independiente (carece de la propiedad de clave externa).
Descubrí la mejor manera de hacer esto sin tener que cargar la propiedad de navegación para que puedas usar Find()
EF y no tengas que piratear.
Use una ID primitiva junto a la propiedad de navegación donde el tipo sea el que sea el tipo de ID de propiedades de navegación (generalmente una cadena para usuarios), por ejemplo:
public partial class LedProject
{
public string ResponsibleUserId { get; set; }
public virtual User ResponsibleUser { get; set; }
}
Actualice la cadena con la propiedad de navegación donde quiera que cree el registro y luego, cuando desee eliminar la relación, simplemente haga ledProject.ResponsibleUserId = null
.
Si asigna un nombre a la ID diferente a las propiedades de navegación nombre + id al final, tendrá que usar anotaciones o api con fluidez, creo.
Más información aquí: ¿En qué escenarios necesito claves externas y propiedades de navegación en el marco de la entidad?
El problema radica en la carga lenta de la propiedad de navegación. Parece que el valor se establece primero en nulo y luego se carga desde la base de datos. Por lo tanto, el valor almacenado actualmente en la base de datos invalida el valor deseado ( nulo en mi caso).
LedProject project = db.LedProject
.Include("ResponsibleUser")
.Where(p => p.ProjectId == projectId)
.FirstOrDefault();
Esto carga el ResponsibleUser cuando se carga el proyecto . ¡Esto finalmente resolvió mi problema!
Me encontré con este problema y se me ocurrió un pequeño "truco" que no rompe la carga perezosa.
Simplemente defina la propiedad en su modelo así:
public int? AccountId { get; set; }
//workaround for entity framework lazy loading problem
Account account;
public virtual Account Account
{
get
{
return account;
}
set
{
account = value;
if (value == null)
{
AccountId = null;
}
}
}
Ahora no tiene que cargar con entusiasmo la propiedad de navegación, y establecerla en nulo funcionará. Más importante aún, al colocar el truco directamente dentro de su entidad, no tendrá que acordarse de realizar verificaciones explícitas en ningún otro lugar del código base.
Puede configurar todas las propiedades de navegación como nulas de esta manera: (necesita tener su Contexto EF), aquí: importado es IEnumerable <YourEntity>
foreach (var entity in imported)
{
foreach (var np in _YourEntityRepository.GetReferenceProperties())
entity.GetType().GetProperty(np.Name).SetValue(entity, null);
}
con GetReferenceProperties definido como:
public IEnumerable<NavigationProperty> GetReferenceProperties()
{
var oc = ((IObjectContextAdapter)Context).ObjectContext;
var entityType = oc.MetadataWorkspace.GetItems(DataSpace.OSpace)
.OfType<EntityType>()
.FirstOrDefault(et => et.Name == typeof(TEntity).Name);
if (entityType != null)
{
foreach (NavigationProperty np in entityType.NavigationProperties
.Where(p => p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One
|| p.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne))
{
yield return np;
}
}
}