I have setup EF+ Audit as follows:
public partial class FocusConnection : DbContext
{
static FocusConnection()
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
(context as FocusConnection).AuditEntries.AddRange(audit.Entries);
}
public override int SaveChanges()
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = base.SaveChanges();
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
base.SaveChanges();
}
return rowAffecteds;
}
public override Task<int> SaveChangesAsync()
{
return SaveChangesAsync(CancellationToken.None);
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
var audit = new Audit();
audit.PreSaveChanges(this);
var rowAffecteds = await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
audit.PostSaveChanges();
if (audit.Configuration.AutoSavePreAction != null)
{
audit.Configuration.AutoSavePreAction(this, audit);
await base.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
}
return rowAffecteds;
}
}
I used a partial class to stay out to the text-templated original context generated by EF6. I used the following SQL script to generate the two table as-is:
CREATE TABLE [dbo].[AuditEntries] (
[AuditEntryID] [int] NOT NULL IDENTITY,
[EntitySetName] [nvarchar](255),
[EntityTypeName] [nvarchar](255),
[State] [int] NOT NULL,
[StateName] [nvarchar](255),
[CreatedBy] [nvarchar](255),
[CreatedDate] [datetime] NOT NULL,
CONSTRAINT [PK_dbo.AuditEntries] PRIMARY KEY ([AuditEntryID])
)
GO
CREATE TABLE [dbo].[AuditEntryProperties] (
[AuditEntryPropertyID] [int] NOT NULL IDENTITY,
[AuditEntryID] [int] NOT NULL,
[RelationName] [nvarchar](255),
[PropertyName] [nvarchar](255),
[OldValue] [nvarchar](max),
[NewValue] [nvarchar](max),
CONSTRAINT [PK_dbo.AuditEntryProperties] PRIMARY KEY ([AuditEntryPropertyID])
)
GO
CREATE INDEX [IX_AuditEntryID] ON [dbo].[AuditEntryProperties]([AuditEntryID])
GO
ALTER TABLE [dbo].[AuditEntryProperties]
ADD CONSTRAINT [FK_dbo.AuditEntryProperties_dbo.AuditEntries_AuditEntryID]
FOREIGN KEY ([AuditEntryID])
REFERENCES [dbo].[AuditEntries] ([AuditEntryID])
ON DELETE CASCADE
GO
I'm getting an error in the static constructor:
static FocusConnection()
{
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
(context as FocusConnection).AuditEntries.AddRange(audit.Entries);
}
Error on AuditEntries.AddRange(audit.Entries):
CS1503 Argument 1: cannot convert from System.Collections.Generic.List<Z.EntityFramework.Plus.AuditEntry> to System.Collections.Generic.IEnumerable<DA.Systems.Focus.Data.EntityFramework.FocusOrm.AuditEntry>
Am I missing something?
You use Database First.
The AuditEntry class from EF+ is not the same as the one generated by your context.
You need to Import value.
AuditManager.DefaultConfiguration.AutoSavePreAction = (context, audit) =>
{
// ADD "Where(x => x.AuditEntryID == 0)" to allow multiple SaveChanges with same Audit
var customAuditEntries = audit.Entries.Select(x => Import(x));
(context as Entities).AuditEntries.AddRange(customAuditEntries);
};
using (var ctx = new Entities())
{
Audit audit = new Audit();
audit.CreatedBy = "ZZZ Projects"; // Optional
ctx.Entity_Basic.Add(new Entity_Basic() {ColumnInt = 2});
ctx.SaveChanges(audit);
}
public AuditEntry Import(Z.EntityFramework.Plus.AuditEntry entry)
{
var customAuditEntry = new AuditEntry
{
EntitySetName = entry.EntitySetName,
EntityTypeName = entry.EntityTypeName,
State = (int)entry.State,
StateName = entry.StateName,
CreatedBy = entry.CreatedBy,
CreatedDate = entry.CreatedDate
};
customAuditEntry.AuditEntryProperties = entry.Properties.Select(x => Import(x)).ToList();
return customAuditEntry;
}
public AuditEntryProperty Import(Z.EntityFramework.Plus.AuditEntryProperty property)
{
var customAuditEntry = new AuditEntryProperty
{
RelationName = property.RelationName,
PropertyName = property.PropertyName,
OldValue = property.OldValueFormatted,
NewValue = property.NewValueFormatted
};
return customAuditEntry;
}
Related
Here's my old code.
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
DatawarehouseEntities db = new DatawarehouseEntities();
// Get JSON from WebHook
JObject data = context.GetDataOrDefault<JObject>();
var tableName = data["Table_Name"].ToString();
var columnNames = db.Database.SqlQuery<string>(String.Format("SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('{0}'); ", tableName)).ToList();
var table = db.GetType().GetProperty(tableName).GetValue(db, null);
var assembly = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(a => a.GetName().Name == "DSI.Data");
var type = assembly.GetTypes().FirstOrDefault(t => t.Name == tableName);
var dbset = Activator.CreateInstance(type);
//var dbset = db.Set(type);
var jsonParams = data.Properties().Select(x => x.Name).ToList();
var selectedColumnNames = columnNames.Intersect(jsonParams);
foreach (var columnName in selectedColumnNames)
{
var property = dbset.GetType().GetProperties().FirstOrDefault(x => x.Name == columnName);
property.SetValue(dbset, data[columnName].ToString(), null);
}
db.Set(type).Add(dbset);
db.SaveChanges();
return Task.FromResult(true);
}
Here's what I try to post http://localhost:port/api/webhooks/incoming/genericjson?code=secret&Table_Name=Table_Name. The type always comes back null. How can I select a table using the string that I pass in?
To make this easier, faster and less error-prone enumerate your entity types and add them to a lookup. EG
public static Dictionary<string, Type> EntityTypesByName { get; } = new Dictionary<string, Type>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
EntityTypesByName.Add(et.Name, et.ClrType);
}
}
This is what finally worked for me.
public override Task ExecuteAsync(string generator, WebHookHandlerContext context)
{
DatawarehouseEntities db = new DatawarehouseEntities();
// Get JSON from WebHook
JObject data = context.GetDataOrDefault<JObject>();
var tableName = data["Table_Name"].ToString();
var columnNames = db.Database.SqlQuery<string>(String.Format("SELECT name FROM sys.columns WHERE object_id = OBJECT_ID('{0}'); ", tableName)).ToList();
var table = db.GetType().GetProperty(tableName).GetValue(db, null);
var assembly = AppDomain.CurrentDomain.GetAssemblies()
.SingleOrDefault(a => a.GetName().Name == "DSI.Data");
var type = assembly.GetTypes().FirstOrDefault(t => t.Name == tableName);
var dbset = Activator.CreateInstance(type);
var jsonParams = data.Properties().Select(x => x.Name).ToList();
var selectedColumnNames = columnNames.Intersect(jsonParams);
foreach (var columnName in selectedColumnNames)
{
var property = dbset.GetType().GetProperties().FirstOrDefault(x => x.Name == columnName);
property.SetValue(dbset, data[columnName].ToString(), null);
}
db.Set(type).Add(dbset);
db.SaveChanges();
return Task.FromResult(true);
}
I got an MVC 5 application that i'm porting to asp.net Core.
In the MVC application call to controller we're made using AngularJS $resource (sending JSON) and we we're POSTing data doing :
ressource.save({ entries: vm.entries, projectId: vm.project.id }).$promise...
that will send a JSON body like:
{
entries:
[
{
// lots of fields
}
],
projectId:12
}
the MVC controller looked like this :
[HttpPost]
public JsonResult Save(List<EntryViewModel> entries, int projectId) {
// code here
}
How can I replicate the same behaviour with .NET Core since we can't have multiple [FromBody]
you cannot have multiple parameter with the FromBody attibute in an action method. If that is need, use a complex type such as a class with properties equivalent to the parameter or dynamic type like that
[HttpPost("save/{projectId}")]
public JsonResult Save(int projectId, [FromBody] dynamic entries) {
// code here
}
As pointed out in the comment, one possible solution is to unify the properties you're posting onto a single model class.
Something like the following should do the trick:
public class SaveModel
{
public List<EntryViewModel> Entries{get;set;}
public int ProjectId {get;set;}
}
Don't forget to decorate the model with the [FromBody] attribute:
[HttpPost]
public JsonResult Save([FromBody]SaveViewModel model)
{
// code here
}
Hope this helps!
It's still rough but I made a Filter to mimic the feature.
public class OldMVCFilter : IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
}
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.HttpContext.Request.Method != "GET")
{
var body = context.HttpContext.Request.Body;
JToken token = null;
var param = context.ActionDescriptor.Parameters;
using (var reader = new StreamReader(body))
using (var jsonReader = new JsonTextReader(reader))
{
jsonReader.CloseInput = false;
token = JToken.Load(jsonReader);
}
if (token != null)
{
var serializer = new JsonSerializer();
serializer.DefaultValueHandling = DefaultValueHandling.Populate;
serializer.FloatFormatHandling = FloatFormatHandling.DefaultValue;
foreach (var item in param)
{
JToken model = token[item.Name];
if (model == null)
{
// try to cast the full body as the current object
model = token.Root;
}
if (model != null)
{
model = this.RemoveEmptyChildren(model, item.ParameterType);
var res = model.ToObject(item.ParameterType, serializer);
context.ActionArguments[item.Name] = res;
}
}
}
}
}
private JToken RemoveEmptyChildren(JToken token, Type type)
{
var HasBaseType = type.GenericTypeArguments.Count() > 0;
List<PropertyInfo> PIList = new List<PropertyInfo>();
if (HasBaseType)
{
PIList.AddRange(type.GenericTypeArguments.FirstOrDefault().GetProperties().ToList());
}
else
{
PIList.AddRange(type.GetTypeInfo().GetProperties().ToList());
}
if (token != null)
{
if (token.Type == JTokenType.Object)
{
JObject copy = new JObject();
foreach (JProperty jProp in token.Children<JProperty>())
{
var pi = PIList.FirstOrDefault(p => p.Name == jProp.Name);
if (pi != null) // If destination type dont have this property we ignore it
{
JToken child = jProp.Value;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, pi.PropertyType);
}
if (!IsEmpty(child))
{
if (child.Type == JTokenType.Object || child.Type == JTokenType.Array)
{
// nested value has been checked, we add the object
copy.Add(jProp.Name, child);
}
else
{
if (!pi.Name.ToLowerInvariant().Contains("string"))
{
// ignore empty value when type is not string
var Val = (string)child;
if (!string.IsNullOrWhiteSpace(Val))
{
// we add the property only if it contain meningfull data
copy.Add(jProp.Name, child);
}
}
}
}
}
}
return copy;
}
else if (token.Type == JTokenType.Array)
{
JArray copy = new JArray();
foreach (JToken item in token.Children())
{
JToken child = item;
if (child.HasValues)
{
child = RemoveEmptyChildren(child, type);
}
if (!IsEmpty(child))
{
copy.Add(child);
}
}
return copy;
}
return token;
}
return null;
}
private bool IsEmpty(JToken token)
{
return (token.Type == JTokenType.Null || token.Type == JTokenType.Undefined);
}
}
Using Unit of Work Repository pattern
private UnitOfWork unitOfWork = new UnitOfWork();
private Entities _Entities = new Entities();
var filing_xml = unitOfWork.T_FILING_XMLRepository.Get().Where(a =>
a.filing_id == filingID).FirstOrDefault();
This is taking around 10 seconds to fetch data
var filing_xml = _Entities.T_FILING_XML.Where(a => a.filing_id == filingID).FirstOrDefault();
This is taking around 2 seconds to fetch data
Is there any solution to make the unit of work faster?
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
The unit of work class
public GenericRepository<T_FILING_XML> T_FILING_XMLRepository
{
get
{
if (this.t_filing_xmlRepository == null)
{
this.t_filing_xmlRepository = new GenericRepository<T_FILING_XML>(context);
}
return t_filing_xmlRepository;
}
}
Code #1
private Entities _Entities = new Entities();
var filing_xml = unitOfWork.T_FILING_XMLRepository.Get().Where(a =>
a.filing_id == filingID).FirstOrDefault();
You are fetching all the records from the database then you filter the result in the application to get only one record. Your Get method has a filter parameter, why not using it?
Using the code below, you will only fetch only one record from the database.
var filing_xml = unitOfWork.T_FILING_XMLRepository.Get(a =>
a.filing_id == filingID).FirstOrDefault();
I am trying to write a Unit test of index method of my ClientController.
Here is my ClientController:
public ClientController(ApplicationUserManager clientManager, ApplicationRoleManager roleManager)
{
ClientManager = clientManager;
RoleManager = roleManager;
}
private ApplicationRoleManager _roleManager;
public ApplicationRoleManager RoleManager
{
get
{
return _roleManager ?? HttpContext.GetOwinContext().Get<ApplicationRoleManager>();
}
set
{
_roleManager = value;
}
}
private ApplicationUserManager _clientManager;
public ApplicationUserManager ClientManager
{
get
{
return _clientManager ?? HttpContext.GetOwinContext().GetUserManager<ApplicationUserManager>();
}
set
{
_clientManager = value;
}
}
public async Task<ActionResult> Index(string filter, string error, string searchName, int? page, int? records)
{
List<string> filterCriteria = new List<string>();
filterCriteria.Add("Choose Email");
var listClients = new List<ApplicationUser>();
// Get the list of clients ( users with client role )
foreach (var user in ClientManager.Users.Where(u => u.IsActive == true && (u.FirstNames.Contains(searchName) || u.LastName.Contains(searchName)
|| searchName == null)).OrderBy(u => u.FirstNames).ToList())
{
if (await ClientManager.IsInRoleAsync(user.Id, "Client"))
{
listClients.Add(user);
filterCriteria.Add(user.Email);
}
}
ViewBag.emails = new SelectList(filterCriteria);
ViewBag.error = error;
if (filter == null || filter.Equals("Choose Email"))
{
return View(listClients.ToList().ToPagedList(page ?? 1, records ?? 15));
}
else
{
return View();
}
And here is my attempt to write a unit test of it.
[TestMethod]
public void Index_Get_RetrievesAllClientFromRepository()
{
// Arrange,
ApplicationUser Client1 = GetClientNamed("1", 1, 1, DateTime.Now, "Abc", "Abc", "Xyz", "343433443", "abc#xyz.com", "M", "06091980-ABSD");
var userStore = new Mock<IUserStore<ApplicationUser>>();
var userManager = new UserManager<ApplicationUser>(userStore.Object);
userStore.Setup(x => x.CreateAsync(Client1))
.Returns(Task.FromResult(IdentityResult.Success));
userStore.Setup(x => x.FindByNameAsync(Client1.UserName))
.Returns(Task.FromResult(Client1));
var roleStore = new Mock<IRoleStore<IdentityRole>>();
var roleManager = new Mock<ApplicationRoleManager>(roleStore.Object);
var controller = new ClientController(
userStore.Object as ApplicationUserManager, roleManager.Object);
// Act
var resultTask = controller.Index("Choose Email", "", "", 1, 15);
resultTask.Wait();
var result = resultTask.Result;
var model = (List<ApplicationUser>)((ViewResult)result).Model;
CollectionAssert.Contains(model, Client1);
}
userStore.Object always come null. I am quite newbie in unit testing and I have looked for many solution but there isn't such use case. Any help would be appreciated.
userStore.Object is coming as null because of the as cast, which returns null if the conversion isn't possible.
userStore is defined as:
new Mock<IUserStore<ApplicationUser>>();
which means that .Object will be of type IUserStore<ApplicationUser>,
which isn't an ApplicationUserManager, so you end up with null.
I'm using asp.net mvc with linq to sql repositories and the following code is throwing an mvc System.Data.Linq.DuplicateKeyException exception on this._table.Attach(entity)
My code is something like that:
public ActionResult Edit(int id)
{
return View(_controllerRepository.GetById(id));
}
public ActionResult Edit(Directivo entity)
{
try
{
_repository.Save(entity, this.UserName)
}
catch (Exception ex)
{
return View(ex);
}
}
And in the repository:
public virtual void Save(T entity, string userName)
{
if (0 == entity.Id)
{
entity.UsuarioIntroduccion = userName;
entity.FechaIntroduccion = DateTime.Now;
entity.UsuarioModificacion = null;
entity.FechaModificacion = null;
this._table.InsertOnSubmit(entity);
}
else
{
entity.UsuarioModificacion = userName;
entity.FechaModificacion = DateTime.Now;
this._table.Attach(entity);
this._table.Context.Refresh(RefreshMode.KeepCurrentValues, entity);
}
try
{
this._dataContext.SubmitChanges();
}
catch (SqlException ex)
{
throw new DataContextException(ex);
}
}
Note that the Id isn't 0.
Its really weird because it happens only with this class, i have a couple more that are working well.
The table is that:
CREATE TABLE [Directivo](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Nombre] [varchar](45) NOT NULL,
[Apellidos] [varchar](60) NOT NULL,
[FechaNacimiento] [datetime] NULL,
[CargoDirectivoId] [int] NOT NULL,
[PathImagen] [varchar](250) NULL,
FechaIntroduccion datetime not null,
UsuarioIntroduccion varchar(45) not null,
FechaModificacion datetime,
UsuarioModificacion varchar(45),
PRIMARY KEY (Id),
FOREIGN KEY (CargoDirectivoId)
REFERENCES CargoDirectivo(Id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
)
And the class is the autogenerated by linq and a partial class that makes it inherit an interface, and sets the buddy class for metadata to use xVal
Do you have any clues about what could be happening?
Thanks in advance!
I think the problem is in this._table.Attach(entity); move this line up before setting the new values as follows
public virtual void Save(T entity, string userName)
{
if (0 == entity.Id)
{
entity.UsuarioIntroduccion = userName;
entity.FechaIntroduccion = DateTime.Now;
entity.UsuarioModificacion = null;
entity.FechaModificacion = null;
this._table.InsertOnSubmit(entity);
}
else
{
this._table.Attach(entity);
entity.UsuarioModificacion = userName;
entity.FechaModificacion = DateTime.Now;
this._table.Context.Refresh(RefreshMode.KeepCurrentValues, entity);
}
try
{
this._dataContext.SubmitChanges();
}
catch (SqlException ex)
{
throw new DataContextException(ex);
}
}
and this may help you http://www.richardbushnell.net/2008/02/18/how-to-update-data-with-linq-to-sql/