2013-09-27 9 views
5

Potrzebuję sposobu na rozróżnienie wyjątków SQL za pomocą obiektu LINQ podmiotu, na przykład, jak odróżnić naruszenie ograniczenia dotyczącego ograniczenia klucza lub wyjątkowego naruszenia ograniczenia, gdy wszystko, co otrzymam z wyjątku DbUpdateException, jest toną zagnieżdżonego wyjątki i bezużyteczne długie komunikaty o błędach? Czy są jakieś wyjątki niższego poziomu, gdzie mogę zrobić coś w stylu "Catch FKException"; złap "uniqueException" lub coś w tym stylu.Obsługa wyjątków w strukturze encji 4

+0

Często wewnętrzne wyjątki będą zawierać numeryczne kody błędów, czy próbowałeś je sprawdzić? Opublikuj przykład wyjątku w swoim pytaniu, podkreśl tekst i kliknij przycisk '{}, aby go ładnie sformatować. –

Odpowiedz

5

pomocą SQL kody błędów ...

catch (DbUpdateException ex) 
        { 
         var sqlex = ex.InnerException.InnerException as SqlException; 

         if (sqlex != null) 
         { 
          switch (sqlex.Number) 
          { 
           case 547: throw new ExNoExisteUsuario("No existe usuario destino."); //FK exception 
           case 2627: 
           case 2601: 
            throw new ExYaExisteConexion("Ya existe la conexion."); //primary key exception 

           default: throw sqlex; //otra excepcion que no controlo. 


          } 
         } 

         throw ex; 
        } 
11
  try 
      { 
       //code 
      } 
      catch (System.Data.Entity.Validation.DbEntityValidationException e) 
      { 
       string rs = ""; 
       foreach (var eve in e.EntityValidationErrors) 
       { 
        rs = string.Format("Entity of type \"{0}\" in state \"{1}\" has the following validation errors:", eve.Entry.Entity.GetType().Name, eve.Entry.State); 
        Console.WriteLine(rs); 

        foreach (var ve in eve.ValidationErrors) 
        { 
         rs += "<br />" + string.Format("- Property: \"{0}\", Error: \"{1}\"", ve.PropertyName, ve.ErrorMessage); 
        } 
       } 
       throw new Exception(rs); 
      } 
+4

Jeśli chcesz być naprawdę miły, ostatnią linią powinno być 'throw new Exception (rs, e)', aby następna osoba w łańcuchu mogła sprawdzić wewnętrzny wyjątek i zobaczyć stos stacktrace i cokolwiek innego. (Również wyrzucanie niestandardowego wyjątku zamiast ogólnego "Wyjątku" byłoby również miłe) –

+0

Ale naprawdę muszę je rozróżnić, muszę wyrzucać różne wyjątki w zależności od rodzaju błędu bazy danych. – user1777914

1

pisałem kilka metod narzędziowych do tego:

public static class DbUtils 
{ 
    /// <summary> 
    ///  Takes a code block that updates database, runs it and catches db exceptions. If the caught 
    ///  exception is one of those that are ok to ignore (okToIgnoreChecks) then no 
    ///  exception is raised and result is returned. Otherwise an exception is rethrown. 
    /// 
    ///  This function is intended to be run within an explicit transaction, i.e.: 
    ///  using (var transaction = db.Database.BeginTransaction()), which should be committed/rolledback afterwards. 
    ///  Otherwise, if you don't use a transaction discard the db context or in other words make this operation 
    ///  the only one that you run within implicit transaction. 
    /// 
    ///  This function can wrap a single DB statement, but it's more efficient to wrap multiple statements 
    ///  so that locks are held for shorter period of time. 
    ///  If an exception occurs within a transaction and is caught by this function, all other changes 
    ///  will be still saved to DB on commit if transaction is used. 
    /// </summary> 
    /// <typeparam name="T">Any result returned by the code block</typeparam> 
    /// <param name="context">Database connection</param> 
    /// <param name="dbCodeBlock"> 
    ///  Code block to execute that updates DB. It's expected, but not critical that 
    ///  this code does not throw any other exceptions. Do not call SaveChanges() from the code block itself. Let this 
    ///  function do it for you. 
    /// </param> 
    /// <param name="okToIgnoreChecks"> 
    ///  List of functions that will check if an exception can be ignored. 
    /// </param> 
    /// <returns>Returns number of rows affected in DB and result produced by the code block</returns> 
    public static Tuple<int, T> IgnoreErrors<T>(DbContext context, 
     Func<T> dbCodeBlock, params Func<DbUpdateException, bool>[] okToIgnoreChecks) 
    { 
     var result = dbCodeBlock(); 
     try 
     { 
      var rowsAffected = context.SaveChanges(); 
      return Tuple.Create(rowsAffected, result); 
     } 
     catch (DbUpdateException e) 
     { 
      if (okToIgnoreChecks.Any(check => check(e))) 
       return Tuple.Create(0, result); 
      throw; 
     } 
    } 

    public static bool IsDuplicateInsertError(DbUpdateException e) 
    { 
     return GetErrorCode(e) == 2601; 
    } 

    public static bool IsForeignKeyError(DbUpdateException e) 
    { 
     return GetErrorCode(e) == 547; 
    } 

    public static T UpdateEntity<T>(DbContext context, T entity, Action<T> entityModifications) 
     where T : class 
    { 
     return EntityCrud(context, entity, (db, e) => 
     { 
      db.Attach(e); 
      entityModifications(e); 
      return e; 
     }); 
    } 

    public static T DeleteEntity<T>(DbContext context, T entity) 
     where T : class 
    { 
     return EntityCrud(context, entity, (db, e) => db.Remove(e)); 
    } 

    public static T InsertEntity<T>(DbContext context, T entity) 
     where T : class 
    { 
     return EntityCrud(context, entity, (db, e) => db.Add(e)); 
    } 

    public static T EntityCrud<T>(DbContext context, T entity, Func<DbSet<T>, T, T> crudAction) 
     where T : class 
    { 
     return crudAction(context.Set<T>(), entity); 
    } 
} 

Oto jak można go używać. Przykład wstawienia potencjalnie zduplikowanego wiersza:

DbUtils.IgnoreErrors(_db,() => DbUtils.InsertEntity(_db, someEntity), 
    DbUtils.IsDuplicateInsertError); 

Żaden wyjątek nie zostanie zgłoszony.

Podobny do poprzedniego przykładu, ale obsłużyć FK naruszenie wyjątkiem wyraźnie:

 try 
     { 
      var numInserted = DbUtils.IgnoreErrors(_db,() => DbUtils.InsertEntity(_db, someEntity), DbUtils.IsDuplicateInsertError).Item1; 
      // no FK exception, but maybe unique index violation, safe 
      // to keep going with transaction 
     } 
     catch (DbUpdateException e) 
     { 
      if (DbUtils.IsForeignKeyError(e)) 
      { 
       // you know what to do 
      } 
      throw; // rethrow other db errors 
     } 

Ostatecznie można nazwać popełnienia transakcję, jeśli masz wyraźne transakcji, poza Zapisz został nazwany już od kontekstu.