How do I programmatically add records to an Umbraco v8 form? - umbraco

I'm looking to add records to an Umbraco v8 form. I know I need the form guid. Is this how I'd do it? Something like this?
public void PostFormData()
{
Guid FormGuid = new Guid("8494a8f0-94da-490e-bd61-7e658c226142");
var form = _formService.Get(FormGuid);
//place for field data into fieldDic
var fieldDic = new Dictionary<Guid, RecordField>();
var firstName = form.AllFields.First(f => f.Alias == "firstName");
var firstNameRecord = new RecordField(firstName);
firstNameRecord.Values = new List<object>() { "Mad Max" };
fieldDic.Add(firstName.Id, firstNameRecord);
var record = new Record()
{
Created = DateTime.Now,
Form = form.Id,
RecordFields = fieldDic,
State = FormState.Submitted,
};
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
}

Here's how I do it. Note, I'm hard-coding the Record.UmbracoPageId to -1 while you might want to actually pass in the correct page ID.
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Forms.Core.Data.Storage;
using Umbraco.Forms.Core.Models;
using Umbraco.Forms.Core.Persistence.Dtos;
using Umbraco.Forms.Core.Services;
namespace myProject.Services
{
public class FormServiceComposer : IUserComposer
{
public void Compose(Composition composition)
{
composition.Register<IFormService, FormService>(Lifetime.Request);
}
}
public interface IFormService
{
void InsertFormData(Guid formGuid, object formModel, string ipAddress);
}
public class FormService : IFormService
{
private readonly ILogger _logger;
private readonly Umbraco.Forms.Core.Services.IFormService _formService;
private readonly IRecordStorage _recordStorage;
private readonly IRecordFieldStorage _recordFieldStorage;
private readonly IWorkflowService _workflowService;
public FormService(ILogger logger, Umbraco.Forms.Core.Services.IFormService formService, IRecordStorage recordStorage, IRecordFieldStorage recordFieldStorage, IWorkflowService workflowService)
{
_logger = logger;
_formService = formService;
_recordStorage = recordStorage;
_recordFieldStorage = recordFieldStorage;
_workflowService = workflowService;
}
#region IFormService
public void InsertFormData(Guid formGuid, object formModel, string ipAddress)
{
try
{
Form form = _formService.GetForm(formGuid);
Record record = new Record();
foreach (Field field in form.AllFields)
{
string caption = CleanCaption(field.Caption);
if (formModel.GetType().GetProperty(caption) == null) continue;
var propertyValue = formModel.GetType().GetProperty(caption).GetValue(formModel, null);
if (propertyValue != null)
{
List<object> values = ExtractValues(propertyValue);
RecordField recordField = new RecordField
{
Alias = field.Alias,
FieldId = field.Id,
Field = field,
Key = Guid.NewGuid(),
Record = record.Id,
Values = values
};
_recordFieldStorage.InsertRecordField(recordField);
record.RecordFields.Add(recordField.Key, recordField);
}
}
record.Form = formGuid;
record.IP = ipAddress;
record.UmbracoPageId = -1;
record.State = Umbraco.Forms.Core.Enums.FormState.Approved;
record.RecordData = record.GenerateRecordDataAsJson();
_recordStorage.InsertRecord(record, form);
_recordStorage.DisposeIfDisposable();
}
catch (Exception ex)
{
_logger.Error<FormService>(ex, "Failed inserting Umbraco Forms data for {formGuid}");
}
}
#endregion IFormService
#region Private
private string CleanCaption(string caption)
{
Regex rgx = new Regex("[^a-zA-Z0-9 -]");
return rgx.Replace(caption.Trim().Replace(" ", ""), "");
}
private List<object> ExtractValues(object propertyValue)
{
List<object> result = new List<object>();
if (propertyValue is string == false && propertyValue.GetType().GetGenericTypeDefinition() == typeof(List<>))
{
IEnumerable<object> _propertyValue = (IEnumerable<object>)propertyValue;
if (_propertyValue.Any())
{
if (_propertyValue.First().GetType().GetProperties().Count() > 1)
{
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
string _value = string.Empty;
foreach (var _property in _propertyValue.First().GetType().GetProperties())
{
string _key = _property.Name;
_value = _value + (_value == "" ? "" : " - ") + item[_key].ToString();
}
result.Add(_value);
}
}
else
{
string _key = _propertyValue.First().GetType().GetProperties().First().Name;
JArray _properties = JArray.Parse(JsonConvert.SerializeObject(propertyValue));
foreach (JToken item in _properties)
{
result.Add(item[_key].ToString());
}
}
}
}
else
{
result.Add(propertyValue);
}
return result;
}
#endregion Private
}
}

Related

AcquireTokenAsync timeouts Sometimes after some days #AzureActiveDirectory #ASP.NET MVC

I have followed all related threads for deadlocking the AcquireTokenAsync operation in ASP.NET MVC. But still I am facing the timeout issue - sometimes after 1 day, sometimes after 3 days. When I restart my web app all works fine again.
Here is my Token Bearer Class which retrieves the token:
public static class SSASTokenBearer
{
public static string Token = string.Empty;
public static DateTime TokenExpiryTime = DateTime.MinValue;
static SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1);
public static bool isTokenGenerated = false;
public static int _counter = 0;
public static bool IsThirdAttempt { get; set; }
public static List<string> lstToken = new List<string>();
public async static Task<string> GetAppOnlyAccessToken(string domain, string resourceUrl, string clientId, string clientSecret, string authUrl)
{
if (TokenExpiryTime > DateTime.UtcNow)
{
//if (_counter.Equals(Convert.ToInt32(Attempt.First)))
//{
// isTokenGenerated = false;
//}
return Token;
}
else
{
await semaphoreSlim.WaitAsync();
//ClearTokenListAndAttemptCounter();
try
{
if (TokenExpiryTime < DateTime.UtcNow)
{
_counter++;
var authority = $"{authUrl}/{domain}/oauth2/token";
var authContext = new AuthenticationContext(authority);
// Config for OAuth client credentials
var clientCred = new ClientCredential(clientId, clientSecret);
try
{
AuthenticationResult authenticationResult = await authContext.AcquireTokenAsync(resourceUrl, clientCred).ConfigureAwait(false);
//get access token
TokenExpiryTime = authenticationResult.ExpiresOn.DateTime;
Token = authenticationResult.AccessToken;
//lstToken.Add(Token);
//isTokenGenerated = true;
}
catch (AdalException ex)
{
throw ex;
}
}
}
finally
{
semaphoreSlim.Release();
}
}
return Token;
}
}
Here is the actual calling of the Bearer Token Class in the Open() method
using Microsoft.AnalysisServices.AdomdClient;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BMIS2.Data.Repositories.PreventativeMaintenance.Dax
{
public enum Attempt
{
First = 1,
Second = 2
}
public abstract class AbstactDal
{
public readonly string BMIS2DataBaseAzureSSAS = ConfigurationManager.AppSettings["BMIS2DataBaseAzureSSAS"];
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
//change the connection password and url dynamically and use initial catalog from web.config
private static string AzureSSASClientId;
private static string AzureSSASClientSecret;
private static string AzureSSASDomain;
private static string AzureSSASURL = Helper.AzureSSASURL;
private static string AzureAuthUrl;
protected AdomdConnection DaxConnection = null;
public AdomdCommand DaxCommand = null;
private static readonly object padlock = new object();
//private static Task<string> tskToken = null;
private bool switchConnection = Convert.ToBoolean(ConfigurationManager.AppSettings["SwitchConnection"]);
private static string ConnectionStr = ConfigurationManager.AppSettings["BMIS2DataBaseAzureSSASStatic"];
//public async Task Execute(string query, Func<T> MethodName)
public async Task ExecuteQuery(string query, Action MethodName)
{
if (switchConnection)
{
await Open(ConnectionStr);
}
else
{
await Open($"Provider=MSOLAP;Data Source={AzureSSASURL};Initial Catalog={Helper.SSASDB};User ID=;Password={await Init()};Persist Security Info=True;Impersonation Level=Impersonate");
}
await ExecuteDaxReader(query, MethodName);
Close();
}
private async Task<string> Init()
{
AzureSSASClientId = Helper.AzureSSASClientId;
AzureSSASClientSecret = Helper.AzureSSASClientSecret;
AzureSSASDomain = Helper.AzureSSASDomain;
AzureAuthUrl = Helper.AzureAuthUrl;
var token= await SSASTokenBearer.GetAppOnlyAccessToken(AzureSSASDomain, $"https://{Helper.AzureSSASZone}", AzureSSASClientId, AzureSSASClientSecret, AzureAuthUrl);
return token;
}
private async Task Open(string BMIS2DataBaseAzureSSAS)
{
DaxConnection = new AdomdConnection(BMIS2DataBaseAzureSSAS);
try
{
DaxConnection.Open();
}
catch (Exception ex)
{
Log.Warn(ex.Message.ToString());
await Open($"Provider=MSOLAP;Data Source={AzureSSASURL};Initial Catalog={Helper.SSASDB};User ID=;Password={ await Init()};Persist Security Info=True;Impersonation Level=Impersonate");
}
DaxCommand = new AdomdCommand();
DaxCommand.Connection = DaxConnection;
}
private void Close()
{
DaxConnection.Close();
}
public abstract Task ExecuteDaxReader(string query, Action MethodName);
}
}
In the implementation repository, each repository has its own common method to execute and read data from the data reader. We retrieve the DAX query from sql db and hit the same query to the SSAS Server.
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using BMIS2.Common.CacheProviders;
using BMIS2.Data.Repositories.PreventativeMaintenance.Dax;
using BMIS2.Entity.ProcessPerformance;
using Microsoft.AnalysisServices.AdomdClient;
namespace BMIS2.Data.Repositories.PreventativeMaintenance.Imp
{
public class DayOfTheWeekRepository : AbstactDal, IDayOfTheWeekRepository
{
public readonly string BMIS2DataBase = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
public readonly int DefaultQuerySessionTime = Convert.ToInt32(ConfigurationManager.AppSettings["DefaultQuerySessionTime"]);
private readonly ICacheProvider _cacheProvider;
private List<AbstractProcessPerformanceDayOfTheWeek> lstRoCont = null;
private bool IsROCount=false;
public DayOfTheWeekRepository(ICacheProvider cacheProvider)
{
_cacheProvider = cacheProvider;
}
public void GetIsRoCount()
{
try
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
IsROCount = ((reader["FACT_ROSale[RoCount]"] == null || reader["FACT_ROSale[RoCount]"].ToString() == "") ? 0 : Convert.ToInt32(reader["FACT_ROSale[RoCount]"])) > 0 ? true : false;
}
}
}
catch(Exception ex)
{
Log.Error(ex.Message.ToString());
throw ex;
}
}
public static bool HasValue( double value)
{
return !Double.IsNaN(value) && !Double.IsInfinity(value);
}
public void GetResultForRoCount()
{
try
{
lstRoCont = new List<AbstractProcessPerformanceDayOfTheWeek>();
if (IsROCount)
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
lstRoCont.Add(new ROCount()
{
DayOfTheWeek = (reader["[Day of Week]"] == null || reader["[Day of Week]"].ToString() == "") ? "" : Convert.ToString(reader["[Day of Week]"]),
TestCount = (reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[Test Count]"]),
RoCount = (reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[RO Count]"]),
RoTestedPercent = Math.Round((reader["[RO Tested %]"] == null || reader["[RO Tested %]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Tested %]"]),1)
//RoTestedPercent = HasValue(Math.Round((((reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count]"])) / ((reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Count]"]))) * 100, 1)) ? Math.Round(((reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count]"])) / ((reader["[RO Count]"] == null || reader["[RO Count]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[RO Count]"])) * 100, 1) : 0,
});
}
}
}
else
{
using (var reader = DaxCommand.ExecuteReader())
{
while (reader.Read())
{
lstRoCont.Add(new NoROCount()
{
DayOfTheWeek = (reader["[Day of Week]"] == null || reader["[Day of Week]"].ToString() == "") ? "" : Convert.ToString(reader["[Day of Week]"]),
//TotalCountPercent = HasValue(totalSum)? Math.Round((totalSum * 100),1) : 0,
TotalCountPercent = Math.Round((reader["[Test Count %]"] == null || reader["[Test Count %]"].ToString() == "") ? 0 : Convert.ToDouble(reader["[Test Count %]"]), 1),
TestCount = (reader["[Test Count]"] == null || reader["[Test Count]"].ToString() == "") ? 0 : Convert.ToInt32(reader["[Test Count]"])
});
}
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message.ToString());
throw ex;
}
}
public async Task<List<AbstractProcessPerformanceDayOfTheWeek>> GetDayOfTheWeekData(DayOfWeekFiltersObject filterSearch,bool IsRo)
{
IsROCount = IsRo;
string RowCountQuery = string.Empty;
// Stopwatch sw = new Stopwatch();
//sw.Start();
try {
using (var con = Database.GetConnection(BMIS2DataBase))
{
con.Open();
using (var command = new SqlCommand())
{
SqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = IsRo? "[BMIS].[R_SP_GetRoCountQuery]" : "[BMIS].[R_SP_GetNoRoCountQuery]";
cmd.Parameters.Add(new SqlParameter("#clientid", filterSearch.ClientId));
cmd.Parameters.Add(new SqlParameter("#StartYear", DateTime.Parse(filterSearch.StartDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#EndYear", DateTime.Parse(filterSearch.EndDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#LocationId", filterSearch.SelectedLocationId));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#ToolType", filterSearch.ToolTypeName));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#TestType", filterSearch.TestType));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#VehicleStatus", filterSearch.VehicleStatusName==null?null:String.Join(",", filterSearch.VehicleStatusName)));
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
RowCountQuery = reader.GetString(reader.GetOrdinal("query"));
}
}
}
}
}
catch(Exception ex)
{
Log.Error(ex.Message);
}
//sw.Stop();
await this.ExecuteQuery(RowCountQuery, GetResultForRoCount);
return lstRoCont;
}
public async Task<bool> IsRowCount(DayOfWeekFiltersObject filterObj)
{
//HttpContext.Current.Session["ClientIdRoCount"] = ClientId;
string RowCountQuery = string.Empty;
using (var con = Database.GetConnection(BMIS2DataBase))
{
con.Open();
using (var command = new SqlCommand())
{
SqlCommand cmd = con.CreateCommand();
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "[BMIS].[R_SP_IsRoCountQuery]";
cmd.Parameters.Add(new SqlParameter("#clientid", filterObj.ClientId));
cmd.Parameters.Add(new SqlParameter("#StartYear", DateTime.Parse(filterObj.StartDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#EndYear", DateTime.Parse(filterObj.EndDate).ToString("yyyyMMdd")));
cmd.Parameters.Add(new SqlParameter("#LocationId", filterObj.SelectedLocationId));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#ToolType", filterObj.ToolTypeName));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#TestType", filterObj.TestType));
cmd.Parameters.Add(SetDbNull.SetDBNullIfEmpty("#VehicleStatus", filterObj.VehicleStatusName==null?null:String.Join(",",filterObj.VehicleStatusName)));
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
RowCountQuery = reader.GetString(reader.GetOrdinal("query"));
}
}
}
}
await this.ExecuteQuery(RowCountQuery, GetIsRoCount);
return IsROCount;
}
public override async Task ExecuteDaxReader(string query, Action MethodName)
{
DaxCommand.CommandText = query;
MethodName();
}
}
}
This is how there are 20 repositories that are implementing the same Abstract Dal.
I would be extremely thankful if anyone can help me resolve this issue.
Try testing your code by setting your TokenCache to null in the AuthenticationContext constructor and removing the ConfigureAwait(false). Without ConfigureAwait(false) it should deadlock immediately and with ConfigureAwait(false) it should work every time. Have you ensured that you are hitting that section?
Apparently this is an issue with later versions. One workaround is to downgrade to version 2.19.208020213
https://github.com/Azure/azure-sdk-for-net/issues/1432
I have gone through each Repository 1 by 1 and founded in few cases my colleagues has not put await to async functions which was causing deadlock. I have put this under monitoring and yes we have put ConfigureAwait(false).
I really appreciate your comment. Thanks

EntityState.Modify instead of Delete in my database

I am using AuditTrail for logging but I have problem. I tried this method ;
http://kamoga.net/audit-trail-and-entity-change-tracking-using-entity-framework-dbcontext/
But I don't delete record for delete operations. I have 'IsDeleted' column and set this column true.
If I use EntityState.Modified in my ActionResult, my 'Action' column value set 'U' but I want to set'D' in database.
[HttpPost, ActionName("Delete")]
public ActionResult DeleteConfirmed(int id)
{
dbContext = new dbContext();
Gallery gallery = dbContext.Gallery.Find(id);
gallery.IsDeleted = true;
dbContext.Entry(gallery).State = System.Data.Entity.EntityState.Modified;
dbContext.SaveChanges();
}
My Enum;
public enum AuditActions
{
I,
U,
A
}
AuditTrail.cs ;
public class AuditTrail
{
public int Id { get; set; }
public string TableName { get; set; }
public string UserName { get; set; }
public string Actions { get; set; }
public string OldData { get; set; }
public string NewData { get; set; }
public string ChangedColums { get; set; }
public string TableIdValue { get; set; }
}
AuditFactoryTrail.cs;
public AuditTrail GetAudit(DbEntityEntry entry)
{
AuditTrail audit = new AuditTrail();
audit.UserName = "Current User"; //You can pass the current user as a parameter
audit.TableName = GetTableName(entry);
audit.TableIdValue = GetKeyValue(entry);
//entry is Added
if (entry.State == EntityState.Added)
{
var newValues = new StringBuilder();
SetAddedProperties(entry, newValues);
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.I.ToString();
}
//entry in deleted
else if (entry.State == EntityState.Deleted)
{
var oldValues = new StringBuilder();
SetDeletedProperties(entry, oldValues);
audit.OldData = oldValues.ToString();
audit.Actions = AuditActions.D.ToString();
}
//entry is modified
else if (entry.State == EntityState.Modified)
{
var oldValues = new StringBuilder();
var newValues = new StringBuilder();
SetModifiedProperties(entry, oldValues, newValues);
audit.OldData = oldValues.ToString();
audit.NewData = newValues.ToString();
audit.Actions = AuditActions.U.ToString();
var modifiedProperties = entry.CurrentValues.PropertyNames.Where(propertyName => entry.Property(propertyName).IsModified).ToList();
var properties = string.Join("||", modifiedProperties.ToList());
audit.ChangedColums = properties;
}
return audit;
}
private void SetAddedProperties(DbEntityEntry entry, StringBuilder newData)
{
foreach (var propertyName in entry.CurrentValues.PropertyNames)
{
var newVal = entry.CurrentValues[propertyName];
if (newVal != null)
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
}
}
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
private void SetDeletedProperties(DbEntityEntry entry, StringBuilder oldData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in dbValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
if (oldVal != null)
{
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
}
private void SetModifiedProperties(DbEntityEntry entry, StringBuilder oldData, StringBuilder newData)
{
DbPropertyValues dbValues = entry.GetDatabaseValues();
foreach (var propertyName in entry.OriginalValues.PropertyNames)
{
var oldVal = dbValues[propertyName];
var newVal = entry.CurrentValues[propertyName];
if (oldVal != null && newVal != null && !Equals(oldVal, newVal))
{
newData.AppendFormat("{0}={1} || ", propertyName, newVal);
oldData.AppendFormat("{0}={1} || ", propertyName, oldVal);
}
}
if (oldData.Length > 0)
oldData = oldData.Remove(oldData.Length - 3, 3);
if (newData.Length > 0)
newData = newData.Remove(newData.Length - 3, 3);
}
private string GetKeyValue(DbEntityEntry entry)
{
var objectStateEntry = ((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
string id = "0";
if (objectStateEntry.EntityKey.EntityKeyValues != null)
id = objectStateEntry.EntityKey.EntityKeyValues[0].Value.ToString();
return id;
}
private string GetTableName(DbEntityEntry dbEntry)
{
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
return tableName;
}
private EntityObject CloneEntity(EntityObject obj)
{
DataContractSerializer dcSer = new DataContractSerializer(obj.GetType());
MemoryStream memoryStream = new MemoryStream();
dcSer.WriteObject(memoryStream, obj);
memoryStream.Position = 0;
EntityObject newObject = (EntityObject)dcSer.ReadObject(memoryStream);
return newObject;
}
}
DbContext.cs;
public override int SaveChanges()
{
var auditFactory = new AuditTrailFactory(this);
var entityList = ChangeTracker.Entries().Where(p =>
p.State == EntityState.Added ||
p.State == EntityState.Deleted ||
p.State == EntityState.Modified ||
!(p.Entity is AuditTrail) ||
p.Entity != null);
entityList.ToList().ForEach(entity =>
{
AuditTrail audit = auditFactory.GetAudit(entity);
AuditTrail.Add(audit);
});
return base.SaveChanges();
}

asp.net mvc: .net core: how to map stored proc result set to class fields

I got this simple model class:
public class PrvProduct
{
[Key]
public Int32 ProductId
{
get; set;
}
public Int64 ProductLineId;
public String MfgPartNumber;
public String ProductName;
public String ProductDescription;
}
I'm trying to call a stored proc, using .net core, it works fine, returns a list of PrvProduct objects. problem is: their fields are empty, unless I fill them up myself in code. ProductId is always there, not sure why (maybe because i typed there the [key] attribute?) but the rest are not.
is there a simple way to map class fields to results sets, like in ado.net (i would just do SQLDataAdapter.Fill(MyDataTable) and the MyDataTable fields will have the values by field name)... or do i have to do option 2 below every time?
Many thanks!
string sqlQuery = "EXEC Maint.GetProductList '" + sNameFilter + "'";
//option 1: this gets no value in the fields of each PrvProduct (ProductId gets value maybe because its [key], the others don't)
IQueryable results = _context.Products.FromSql(sqlQuery).AsNoTracking();
//option 2: this works, but... do i have to do this for every stored proc i call, every field, or is there a beter way to map class fields to returned results fields?
List<PrvProduct> oList = new List<PrvProduct>();
using (var command = _context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = sqlQuery;
command.CommandType = CommandType.Text;
_context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
while (result.Read())
{
// Map to your entity
oList.Add(new PrvProduct
{
ProductId = result.GetInt32(0),
ProductName = result.GetString(1)
});
}
}
}
In EF Core, if you execute a stored procedure using one of your DbSet entities then it will map it automatically. The problem is that in many case you need to map a stored procedure to a DTO, for example, and the DTO is not part of your DbSet entities. In those cases you need to go back in time and map it manually which is a waste of time.
In order to avoid mapping the data reader manually, I added a bunch of extension methods that do it for you. The code is not perfect and I'm still improving it but it's good enough in most of the cases.
Once you add the extensions methods I'm gonna describe below, you can use it like this:
return dbContext.Database.SqlQuery<SalesReportDTO>("spGetSalesReport",
SqlParameterBuilder.Build("customerId", customerId),
SqlParameterBuilder.Build("dateFrom", from),
SqlParameterBuilder.Build("dateTo", to)).ToList();
DatabaseFacadeExtensions: adds extensions methods to DatabaseFacade class, allowing you to call the method SqlQuery from dbContext.Database just like we used to do with Entity Framework 6.
public static class DatabaseFacadeExtensions
{
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, null, CommandType.StoredProcedure, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, CommandType commandType, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, null, commandType, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, params SqlParameter[] parameters)
{
return SqlQuery<T>(database, query, commandTimeout, CommandType.StoredProcedure, parameters);
}
public static List<T> SqlQuery<T>(this DatabaseFacade database, string query, int? commandTimeout, CommandType commandType, params SqlParameter[] parameters)
{
using (var cmd = database.GetDbConnection().CreateCommand())
{
cmd.CommandText = query;
cmd.CommandType = commandType;
if (commandTimeout.HasValue)
{
cmd.CommandTimeout = commandTimeout.Value;
}
cmd.Parameters.AddRange(parameters);
if (cmd.Connection.State == System.Data.ConnectionState.Closed)
{
cmd.Connection.Open();
}
try
{
using (var reader = cmd.ExecuteReader())
{
return reader.MapToList<T>();
}
}
finally
{
cmd.Connection.Close();
}
}
}
}
DbDataReaderExtensions: adds extensions methods to DbDataReader class so it can map the data reader to your own clases.
public static class DbDataReaderExtensions
{
public static List<T> MapToList<T>(this DbDataReader dr)
{
var objList = new List<T>();
if (dr.HasRows)
{
bool isSingleValue = typeof(T).IsPrimitive || typeof(T) == typeof(string);
IEnumerable<PropertyInfo> props = null;
Dictionary<string, DbColumn> colMapping = null;
if (!isSingleValue)
{
props = typeof(T).GetRuntimeProperties();
colMapping = dr.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
}
while (dr.Read())
{
T obj;
if (isSingleValue)
{
obj = (T)dr.GetValue(0);
}
else
{
obj = Activator.CreateInstance<T>();
foreach (var prop in props)
{
string propertyName = prop.Name.ToLower();
if (!colMapping.ContainsKey(propertyName))
{
continue;
}
var val = dr.GetValue(colMapping[propertyName].ColumnOrdinal.Value);
if (val != DBNull.Value)
{
// enum property
if (prop.PropertyType.IsEnum)
{
prop.SetValue(obj, Enum.ToObject(prop.PropertyType, val));
}
// nullable enum property
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) && Nullable.GetUnderlyingType(prop.PropertyType).IsEnum)
{
prop.SetValue(obj, Enum.ToObject(Nullable.GetUnderlyingType(prop.PropertyType), val));
}
else
{
prop.SetValue(obj, val);
}
}
}
}
objList.Add(obj);
}
}
return objList;
}
public static T MapToObject<T>(this DbDataReader dr)
{
var props = typeof(T).GetRuntimeProperties();
if (dr.HasRows)
{
var colMapping = dr.GetColumnSchema()
.Where(x => props.Any(y => y.Name.ToLower() == x.ColumnName.ToLower()))
.ToDictionary(key => key.ColumnName.ToLower());
if (dr.Read())
{
T obj = Activator.CreateInstance<T>();
foreach (var prop in props)
{
var val = dr.GetValue(colMapping[prop.Name.ToLower()].ColumnOrdinal.Value);
prop.SetValue(obj, val == DBNull.Value ? null : val);
}
return obj;
}
}
return default(T);
}
}
The next class is optional but I use to build parameters in a simpler way and it's needed in the example I described above:
public class SqlParameterBuilder
{
public static SqlParameter Build(string name, bool? value)
{
if (value.HasValue)
{
return new SqlParameter() { ParameterName = name, Value = value.Value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, int? value)
{
if (value.HasValue)
{
return new SqlParameter() { ParameterName = name, Value = value.Value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, string value)
{
if (value != null)
{
return new SqlParameter() { ParameterName = name, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, DateTime? value)
{
if (value != null)
{
return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.DateTime, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, Guid? value)
{
if (value.HasValue)
{
return new SqlParameter { ParameterName = name, SqlDbType = SqlDbType.UniqueIdentifier, Value = value };
}
return new SqlParameter() { ParameterName = name, Value = DBNull.Value };
}
public static SqlParameter Build(string name, int[] values)
{
SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
par.TypeName = "dbo.IntParameterList";
DataTable dt = new DataTable();
dt.Columns.Add("id", typeof(int));
par.Value = dt;
if (values != null)
{
foreach (int value in values.Where(p => p != 0))
{
dt.Rows.Add(value);
}
}
return par;
}
public static SqlParameter Build(string name, string[] values, VarcharParameterListEnum varcharParameterListType = VarcharParameterListEnum.Varchar50)
{
SqlParameter par = new SqlParameter(name, SqlDbType.Structured);
switch(varcharParameterListType)
{
case VarcharParameterListEnum.Varchar15:
par.TypeName = "dbo.Varchar15ParameterList";
break;
case VarcharParameterListEnum.Varchar50:
par.TypeName = "dbo.Varchar50ParameterList";
break;
case VarcharParameterListEnum.Varchar100:
par.TypeName = "dbo.Varchar100ParameterList";
break;
case VarcharParameterListEnum.Varchar255:
par.TypeName = "dbo.Varchar255ParameterList";
break;
case VarcharParameterListEnum.Varchar510:
par.TypeName = "dbo.Varchar510ParameterList";
break;
}
DataTable dt = new DataTable();
dt.Columns.Add("textValue", typeof(string));
par.Value = dt;
if (values != null)
{
foreach (var value in values.Where(p => !string.IsNullOrWhiteSpace(p)))
{
dt.Rows.Add(value);
}
}
return par;
}
}

How can I create a task automatically by creating a new workitem in TFS?

I would like to know can I create a new linked task when I create a workitem.
Can anyone give a tip of how to do this?
I've gone through some old code that I previously used for this scenario. The following code creates a linked task whenever a new bug is set to approved.
The code filters to a specific Team Project and uses a specific account to connect. You need to enter these before the plugin will work. You can then modify this code to create the tasks you want.
For a general introduction to server plugins and how to turn the code below into a functioning plugin see Extending Team Foundation
using Microsoft.TeamFoundation.Framework.Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.WorkItemTracking.Server;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using Microsoft.TeamFoundation.Client;
using System.Net;
using System.Collections;
namespace TfsExtension.CreateTaskForBug
{
public class CreateTaskForBugEventHandler : ISubscriber
{
const string projectName = "<Enter your project name here>";
public string Name
{
get
{
return "CreateTaskForBugEventHandler";
}
}
public SubscriberPriority Priority
{
get
{
return SubscriberPriority.Normal;
}
}
public EventNotificationStatus ProcessEvent(
TeamFoundationRequestContext requestContext,
NotificationType notificationType,
object notificationEventArgs,
out int statusCode,
out string statusMessage,
out ExceptionPropertyCollection properties)
{
statusCode = 0;
properties = null;
statusMessage = String.Empty;
try
{
ProcessNotification(notificationType, notificationEventArgs, requestContext);
}
catch (Exception exception)
{
TeamFoundationApplicationCore.LogException("Error processing event", exception);
}
return EventNotificationStatus.ActionPermitted;
}
private static void ProcessNotification(NotificationType notificationType, object notificationEventArgs, TeamFoundationRequestContext requestContext)
{
if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent)
{
var ev = notificationEventArgs as WorkItemChangedEvent;
if (ev.PortfolioProject == projectName)
{
string workItemType = (from field in ev.CoreFields.StringFields
where field.Name == "Work Item Type"
select field.NewValue).Single();
if (workItemType == "Bug")
{
ProcessBug(ev, requestContext);
}
}
}
}
private static void ProcessBug(WorkItemChangedEvent ev, TeamFoundationRequestContext requestContext)
{
var stateChange = (from field in ev.ChangedFields.StringFields
where field.Name == "State" && field.NewValue == "Approved"
select field).SingleOrDefault();
if (stateChange != null)
{
AddChildTaskToBug(ev, requestContext);
}
}
private static void AddChildTaskToBug(WorkItemChangedEvent ev, TeamFoundationRequestContext requestContext)
{
WorkItemStore wiStore = GetWorkItemStore(requestContext);
WorkItem witem = wiStore.GetWorkItem(ev.CoreFields.IntegerFields[0].NewValue);
Project teamProject = witem.Project;
int bugID = witem.Id;
string bugTitle = witem.Fields["System.Title"].Value.ToString();
string bugAssignedTo = witem.Fields["System.AssignedTo"].Value.ToString();
string bugAreaPath = witem.Fields["System.AreaPath"].Value.ToString();
string bugIterationPath = witem.Fields["System.IterationPath"].Value.ToString();
string bugChangedBy = witem.Fields["System.ChangedBy"].OriginalValue.ToString();
string bugTeamProject = witem.Project.Name;
string childTaskTitle = "Resolve bug " + bugID + " - " + bugTitle;
if (CreateResolutionTask(wiStore, bugID, childTaskTitle))
{
witem = CreateWorkItem(wiStore, teamProject, bugID, bugTitle, bugAssignedTo, bugAreaPath, bugIterationPath);
if (IsValid(witem))
{
witem.Save();
LinkParentAndChild(wiStore, witem, bugID);
}
}
}
private static bool IsValid(WorkItem witem)
{
ArrayList validationErrors = witem.Validate();
return validationErrors.Count == 0;
}
private static void LinkParentAndChild(WorkItemStore wiStore, WorkItem witem, int bugID)
{
var linkType = wiStore.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Hierarchy];
var parentWorkItem = wiStore.GetWorkItem(bugID);
int taskID = witem.Id;
var childWorkItem = wiStore.GetWorkItem(taskID);
parentWorkItem.Links.Add(new WorkItemLink(linkType.ForwardEnd, childWorkItem.Id));
parentWorkItem.Save();
}
private static WorkItem CreateWorkItem(WorkItemStore wiStore, Project teamProject, int bugID, string bugTitle, string bugAssignedTo, string bugAreaPath, string bugIterationPath)
{
WorkItemTypeCollection workItemTypes = wiStore.Projects[teamProject.Name].WorkItemTypes;
WorkItemType wiType = workItemTypes["Task"];
WorkItem witem = new WorkItem(wiType);
witem.Fields["System.Title"].Value = "Resolve bug " + bugID + " - " + bugTitle;
witem.Fields["System.AssignedTo"].Value = bugAssignedTo;
witem.Fields["System.AreaPath"].Value = bugAreaPath;
witem.Fields["System.IterationPath"].Value = bugIterationPath;
witem.Fields["Microsoft.VSTS.Common.Activity"].Value = "Bug Resolution";
return witem;
}
private static bool CreateResolutionTask(WorkItemStore wiStore, int bugID, string childTaskTitle)
{
WorkItem parentBug = wiStore.GetWorkItem(bugID);
WorkItemLinkCollection links = parentBug.WorkItemLinks;
foreach (WorkItemLink wil in links)
{
if (wil.LinkTypeEnd.Name == "Child")
{
WorkItem childTask = wiStore.GetWorkItem(wil.TargetId);
if ((childTask.Title == childTaskTitle) && (childTask.State != "Closed"))
{
return false;
}
}
}
return true;
}
private static Uri GetTFSUri(TeamFoundationRequestContext requestContext)
{
var locationService = requestContext.GetService<TeamFoundationLocationService>();
return new Uri(locationService.GetServerAccessMapping(requestContext).AccessPoint + "/" + requestContext.ServiceHost.Name);
}
private static WorkItemStore GetWorkItemStore(TeamFoundationRequestContext requestContext)
{
NetworkCredential netCred = new NetworkCredential(
"<username>",
"<password>");
WindowsCredential windowsCred = new WindowsCredential(netCred);
var credentials = new TfsClientCredentials(windowsCred);
credentials.AllowInteractive = true;
var tpc = new TfsTeamProjectCollection(
GetTFSUri(requestContext),
credentials);
tpc.Authenticate();
return tpc.GetService<WorkItemStore>();
}
public Type[] SubscribedTypes()
{
return new Type[1] { typeof(WorkItemChangedEvent) };
}
}
}

Cannot use ComboBox SelectedItem as BindingSource for cascaded ComboBox

I have 2 ComboBoxes on my form. I create the bindings as follows:
TestClass myclass = new TestClass("Instruments");
myclass.Add(instr1 = new TestClass("INSTR1"));
myclass.Add(instr2 = new TestClass("INSTR2"));
myclass.Add(instr3 = new TestClass("INSTR3"));
myclass.Add(instr4 = new TestClass("INSTR4"));
instr1.Add(app1 = new TestClass("app1"));
instr1.Add(app2 = new TestClass("app2"));
instr1.Add(app3 = new TestClass("app3"));
instr1.Add(app4 = new TestClass("app4"));
instr2.Add(app5 = new TestClass("app5"));
instr2.Add(app6 = new TestClass("app6"));
instr2.Add(app7 = new TestClass("app7"));
instr2.Add(app8 = new TestClass("app8"));
mysource = new BindingSource(myclass, null);
selectedComboBox1.DataSource = mysource;
selectedComboBox1.DisplayMember = "NAME";
mysource2 = new BindingSource(selectedComboBox1, "SelectedItem");
selectedComboBox2.DataSource = mysource2;
selectedComboBox2.DisplayMember = "NAME";
The class used for the binding looks as follows
class TestClass : BindingList<TestClass>, INotifyPropertyChanged
{
public event RunTestChanged RunTestChangedEventHandler;
public TestClass()
{
this.test = "";
this.name = "";
this.runTest = true;
}
public TestClass(string name)
{
this.test = "";
this.name = name;
this.runTest = true;
}
public TestClass LIST
{
get
{
return this;
}
}
public string NAME
{
get
{
return this.name;
}
set
{
this.name = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("NAME"));
}
}
}
public string TEST
{
get
{
return this.test;
}
set
{
this.test = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("TEST"));
}
}
}
public bool RUNTEST
{
get
{
return runTest;
}
set
{
runTest = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs("RUNTEST"));
}
RunTestArgs myargs = new RunTestArgs(value);
if (RunTestChangedEventHandler != null)
{
RunTestChangedEventHandler(this, myargs);
}
}
}
private bool runTest;
private string name;
private string test;
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
}
when the form first loads the 2 comboboxes are filled as they should be with the expected items. However, if i change an item in selectedComboBox1, the items in selectedComboBox2 aren't updated. I know that I can subscribe to the selectedComboBox1 SelectedIndexChanged event and then rebind the DataSource on selectedComboBox2 and everything will work as expected.
For example:
void selectedComboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
mysource2.DataSource = selectedComboBox1.SelectedItem;
mysource2.DataMember = null;
}
Another alternative that works is to perform the databinding as follows:
mysource = new BindingSource(myclass, null);
mysource2 = new BindingSource(mysource, "LIST");
mysource3 = new BindingSource(mysource2, "LIST");
selectedComboBox1.DataSource = mysource;
selectedComboBox1.DisplayMember = "NAME";
selectedComboBox2.DataSource = mysource2;
selectedComboBox2.DisplayMember = "NAME";
However I wanted to know if there was a way to avoid having to subscribe to the event or performing the databinding in a different manner and just have the 2nd ComboBox be updated via the BindingSource using the SelectedItem property. In the end I'm curious to know how to get the BindingSource to be updated via the SelectedItem databinding and if it's not possible what is preventing it from working.
Thank you for your help.
i have the same issue and got resolved by binding Name to SelectedValue of combobox and set ValueMember to be "NAME" property
selectedComboBox1.DisplayMember = "NAME";
selectedComboBox1.ValueMember = "NAME";

Resources