Come utilizzare Entity Framework per mappare i risultati di una stored procedure sull’entity framework con parametri con nomi diversi

Sto cercando di creare un esempio di base utilizzando Entity Framework per eseguire la mapping dell’output di una stored procedure di SQL Server a un’ quadro in C #, ma l’ quadro ha parametri di nomi (amichevoli) diversi rispetto ai nomi più criptici. Sto anche cercando di farlo con la syntax Fluent (cioè non edmx).


Cosa funziona ….

La stored procedure restituisce valori chiamati: UT_ID, UT_LONG_NM, UT_STR_AD, UT_CITY_AD, UT_ST_AD, UT_ZIP_CD_AD, UT_CT

Se creo un object come questo …

public class DBUnitEntity { public Int16 UT_ID { get; set; } public string UT_LONG_NM { get; set; } public string UT_STR_AD { get; set; } public string UT_CITY_AD { get; set; } public string UT_ST_AD { get; set; } public Int32 UT_ZIP_CD_AD { get; set; } public string UT_CT { get; set; } } 

e un EntityTypeConfiguration come questo …

 public class DbUnitMapping: EntityTypeConfiguration { public DbUnitMapping() { HasKey(t => t.UT_ID); } } 

… che aggiungo all’OnModelCreating di DbContext, quindi posso ottenere le entity framework correttamente dal database, il che è bello, usando questo ….

 var allUnits = _context.Database.SqlQuery(StoredProcedureHelper.GetAllUnitsProc); 

MA, cosa non funziona

Se voglio un’ quadro come questa, con nomi più amichevoli ….

 public class UnitEntity : IUnit { public Int16 UnitId { get; set; } public string Name { get; set; } public string Address { get; set; } public string City { get; set; } public string State { get; set; } public Int32 Zip { get; set; } public string Category { get; set; } } 

e un EntityTypeConfiguration come questo …

  public UnitMapping() { HasKey(t => t.UnitId); Property(t => t.UnitId).HasColumnName("UT_ID"); Property(t => t.Name).HasColumnName("UT_LONG_NM"); Property(t => t.Address).HasColumnName("UT_STR_AD"); Property(t => t.City).HasColumnName("UT_CITY_AD"); Property(t => t.State).HasColumnName("UT_ST_AD"); Property(t => t.Zip).HasColumnName("UT_ZIP_CD_AD"); Property(t => t.Category).HasColumnName("UT_CT"); } 

Quando provo ad ottenere i dati ottengo System.Data.EntityCommandExecutionException con il messaggio ….

“Il lettore di dati non è compatibile con” DataAccess.EFCodeFirstSample.UnitEntity “specificato. Un membro del tipo” UnitId “non ha una colonna corrispondente nel lettore di dati con lo stesso nome.”

Se aggiungo la proprietà “stored procedure denominata” all’entity framework, essa va e si lamenta della prossima proprietà “sconosciuta”.

“HasColumnName” non funziona come mi aspetto / desidero in questo stile fluente con codice prima memorizzato nello stile EF?


Aggiornare:

Provato usando DataAnnotations (Key da ComponentModel e Column da EntityFramework) … ala

 public class UnitEntity : IUnit { [Key] [Column("UT_ID")] public Int16 UnitId { get; set; } public string Name { get; set; } 

Ciò ha eliminato la necessità di qualsiasi EntityTypeConfiguration per l’object DBUnitEntity con la denominazione identica al database (cioè solo aggiungendo l’attributo [Key]), ma non ha fatto nulla per l’ quadro con i nomi di proprietà che non corrispondono al database (stesso errore come prima).

Non mi interessa usare le annotazioni ComponentModel nel modello, ma non voglio davvero usare le annotazioni EntityFramework nel modello se posso aiutarlo (non voglio bind il modello a qualsiasi specifico quadro di accesso ai dati)

Da Entity Framework Code Primo libro (pagina 155):

Il metodo SQLQuery tenta sempre la corrispondenza colonna-proprietà in base al nome della proprietà … Nessuno che la corrispondenza nome colonna-proprietà non tenga conto di alcuna mapping. Ad esempio, se la proprietà DestinationId è stata mappata a una colonna denominata Id nella tabella Destinazione, il metodo SqlQuery non utilizzerà questa mapping.

Quindi non è ansible utilizzare i mapping quando si chiama stored procedure. Una soluzione alternativa consiste nel modificare la stored procedure per restituire il risultato con alias per ogni colonna che corrisponderà ai nomi delle proprietà dell’object.

Select UT_STR_AD as Address From SomeTable ecc

Questo non sta usando Entity Framework ma è derivato da dbcontext. Ho passato ore e ore a perlustrare internet e ad usare dot sbirciatina tutto per niente. Ho letto alcuni in cui il ColumnAttribute viene ignorato per SqlQueryRaw. Ma ho creato qualcosa con riflessioni, generici, sql datareader e Activator. Ho intenzione di testarlo su alcuni altri procs. Se c’è qualche altro errore di controllo che dovrebbe entrare, commentare.

 public static List SqlQuery( DbContext db, string sql, params object[] parameters) { List Rows = new List(); using (SqlConnection con = new SqlConnection(db.Database.Connection.ConnectionString)) { using (SqlCommand cmd = new SqlCommand(sql, con)) { cmd.CommandType = CommandType.StoredProcedure; foreach (var param in parameters) cmd.Parameters.Add(param); con.Open(); using (SqlDataReader dr = cmd.ExecuteReader()) { if (dr.HasRows) { var dictionary = typeof(T).GetProperties().ToDictionary( field => CamelCaseToUnderscore(field.Name), field => field.Name); while (dr.Read()) { T tempObj = (T)Activator.CreateInstance(typeof(T)); foreach (var key in dictionary.Keys) { PropertyInfo propertyInfo = tempObj.GetType().GetProperty(dictionary[key], BindingFlags.Public | BindingFlags.Instance); if (null != propertyInfo && propertyInfo.CanWrite) propertyInfo.SetValue(tempObj, Convert.ChangeType(dr[key], propertyInfo.PropertyType), null); } Rows.Add(tempObj); } } dr.Close(); } } } return Rows; } private static string CamelCaseToUnderscore(string str) { return Regex.Replace(str, @"(? 

Inoltre, qualcosa da sapere è che tutti i nostri proc memorizzati restituiscono il carattere di sottolineatura minuscolo delimitato. CamelCaseToUnderscore è stato creato appositamente per questo.

Ora BigDeal può mappare a big_deal

Dovresti essere in grado di chiamarlo così

 Namespace.SqlQuery(db, "name_of_stored_proc", new SqlParameter("@param",value),,,,,,,); 

L’esempio pubblicato da “DeadlyChambers” è ottimo, ma vorrei estendere l’esempio per includere ColumnAttribute che è ansible utilizzare con EF per aggiungere a una proprietà per mappare un campo SQL a una proprietà di class.

Ex.

 [Column("sqlFieldName")] public string AdjustedName { get; set; } 

Ecco il codice modificato.
Questo codice include anche un parametro per consentire i mapping personalizzati, se necessario, passando un dizionario.
Avrai bisogno di un convertitore di tipi diverso da Convert.ChangeType per cose come i tipi nullable.
Ex. Se hai un campo che è un po ‘nel database e nullable booleano in. NET otterrai un problema di tipo convert.

 ///  /// WARNING: EF does not use the ColumnAttribute when mapping from SqlQuery. So this is a "fix" that uses "lots" of REFLECTION ///  ///  ///  ///  /// Model Property Name and SQL Property Name /// SQL Parameters ///  public static List SqlQueryMapped(this System.Data.Entity.Database database, string sqlCommandString, Dictionary modelPropertyName_sqlPropertyName, params System.Data.SqlClient.SqlParameter[] sqlParameters) { List listOfT = new List(); using (var cmd = database.Connection.CreateCommand()) { cmd.CommandText = sqlCommandString; if (cmd.Connection.State != System.Data.ConnectionState.Open) { cmd.Connection.Open(); } cmd.Parameters.AddRange(sqlParameters); using (var dataReader = cmd.ExecuteReader()) { if (dataReader.HasRows) { // HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection. var convertTo = typeof(GenericExtensions).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(mi => mi.Name == "ConvertTo").Where(m => m.GetParameters().Count() == 1).FirstOrDefault(); // now build a new list of the SQL properties to map // NOTE: this method is used because GetOrdinal can throw an exception if column is not found by name Dictionary sqlPropertiesAttributes = new Dictionary(); for (int index = 0; index < dataReader.FieldCount; index++) { sqlPropertiesAttributes.Add(dataReader.GetName(index), index); } while (dataReader.Read()) { // create a new instance of T T newT = (T)Activator.CreateInstance(typeof(T)); // get a list of the model properties var modelProperties = newT.GetType().GetProperties(); // now map the SQL property to the EF property foreach (var propertyInfo in modelProperties) { if (propertyInfo != null && propertyInfo.CanWrite) { // determine if the given model property has a different map then the one based on the column attribute string sqlPropertyToMap = (propertyInfo.GetCustomAttribute()?.Name ?? propertyInfo.Name); string sqlPropertyName; if (modelPropertyName_sqlPropertyName!= null && modelPropertyName_sqlPropertyName.TryGetValue(propertyInfo.Name, out sqlPropertyName)) { sqlPropertyToMap = sqlPropertyName; } // find the SQL value based on the column name or the property name int columnIndex; if (sqlPropertiesAttributes.TryGetValue(sqlPropertyToMap, out columnIndex)) { var sqlValue = dataReader.GetValue(columnIndex); // ignore this property if it is DBNull if (Convert.IsDBNull(sqlValue)) { continue; } // HACK: you can't use extension methods without a type at design time. So this is a way to call an extension method through reflection. var newValue = convertTo.MakeGenericMethod(propertyInfo.PropertyType).Invoke(null, new object[] { sqlValue }); propertyInfo.SetValue(newT, newValue); } } } listOfT.Add(newT); } } } } return listOfT; }