I got this class:
public class PedidosList
{
public virtual int ID_Pedido { get; set; }
public virtual int Numero { get; set; }
public virtual DateTime Fecha { get; set; }
public virtual DateTime FechaEntrega { get; set; }
public virtual int ID_Cliente { get; set; }
public virtual string Cliente { get; set; }
public virtual Decimal Bruto { get; set; }
public virtual Decimal Neto { get; set; }
public virtual Boolean Aprobado { get; set; }
public virtual string Observaciones { get; set; }
public virtual Boolean Entregado { get; set; }
}
To represent a subset of fields from a POCOs class map to an SQL Table with Entity Framework. Then use this function to make optional filter using Linq to Entities and return a IENumerable colection:
public IEnumerable<PedidosList> Pedidos_Listar(string sComprobante, Clientes MyCliente = null, DateTime? dDesde = null, DateTime? dHasta = null, bool bCumplidos = false)
{
using (var context = new OhmioEntities())
{
IEnumerable<PedidosList> query =
from Pedidos in context.Pedidos
join Clientes in context.Clientes on Pedidos.ID_Cliente equals Clientes.ID_Cliente
where Pedidos.ID_Comprobante == sComprobante
select new PedidosList {ID_Pedido = Pedidos.ID_Pedido, Fecha=Pedidos.Fecha, Aprobado=Pedidos.Aprobado, Bruto=Pedidos.Bruto, Cliente=Clientes.RazonFantasia,
FechaEntrega=Pedidos.FechaEntrega, Neto=Pedidos.Neto, Numero=Pedidos.Numero, Observaciones=Pedidos.Observaciones, Entregado=Pedidos.Entregado, ID_Cliente=Pedidos.ID_Cliente };
if (MyCliente != null) query = query.Where(i => i.ID_Cliente == MyCliente.ID_Cliente);
if (MyCliente != null) query = query.Where(i => i.ID_Cliente == MyCliente.ID_Cliente);
if (dDesde != null && dHasta != null) query = query.Where(i => i.Fecha >= dDesde && i.Fecha <= dHasta);
if (bCumplidos == false) query = query.Where(i => i.Entregado == false);
return query.ToList();
}
}
So my questions: Is this the best way to achive this? Can i make an optional filter adding a new where on a field that is on Pedidos but not on PedidosList? Example: i have to add the field ID_Cliente to PedidosList ONLY so i can filter even if i don't want it on PedidosList. Thanks!
I'm super late answering this, but I just came across it. I wouldn't be particularly uncomfortable with what you've done, I think it's mostly a matter of personal preference and what you consider readable. But if I were to write it, I would probably do something like this.
public IEnumerable<PedidosList> Pedidos_Listar(string sComprobante, Clientes MyCliente = null, DateTime? dDesde = null, DateTime? dHasta = null, bool bCumplidos = false)
{
using (var context = new OhmioEntities())
{
return from Pedidos in context.Pedidos
join Clientes in context.Clientes on Pedidos.ID_Cliente equals Clientes.ID_Cliente
where (Pedidos.ID_Comprobante == sComprobante) &&
(MyCliente == null || Pedidos.ID_Cliente == MyCliente.ID_Cliente) &&
(dDesde == null || dHasta == null || Pedidos.Fecha >= dDesde && Pedidos.Fecha <= dHasta) &&
(bCumplidos || Pedidosi.Entregado)
select new PedidosList
{
ID_Pedido = Pedidos.ID_Pedido,
Fecha = Pedidos.Fecha,
Aprobado = Pedidos.Aprobado,
Bruto = Pedidos.Bruto,
Cliente = Clientes.RazonFantasia,
FechaEntrega = Pedidos.FechaEntrega,
Neto = Pedidos.Neto,
Numero = Pedidos.Numero,
Observaciones = Pedidos.Observaciones,
Entregado = Pedidos.Entregado,
ID_Cliente = Pedidos.ID_Cliente
};
}
}
The one thing I would be pretty tempted to change on what you did, though, is that I wouldn't include your ToList() at the end. That negates some of the benefits of using IEnumerable<T>.
Related
Not really sure why I'm not getting the child object populated.
My tables:
Product:
[ProductId]
,[Brand]
,[Model]
StoreProduct:
[StoreId]
,[ProductId]
,[StoreProductId]
Class
public class Product
{
public Guid ProductId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public virtual List<StoreProduct> StoreProducts { get; set; }
}
public class StoreProduct
{
public int StoreId { get; set; } //Key 0
public Guid ProductId { get; set; } //Key 1
public Store Store { get; set; }
public Product Product { get; set; }
public string StoreProductId { get; set; } //A new Id specific for each store
}
My Dapper Code
string sql = "SELECT * FROM StoreProduct AS A INNER JOIN Product AS B ON A.ProductId = B.ProductId WHERE A.StoreProductId = #StoreProductId and A.StoreId = #StoreId";
var connection = AppDbContext.Database.GetDbConnection();
return connection.Query<StoreProduct, Product, Product>(
sql,
(StoreProduct, Product) => { StoreProduct.ProductId = Product.ProductId; return Product; },
new { StoreProductId = storeProductId, StoreId = StoreID }, splitOn: "ProductId")
.FirstOrDefault();
What the DB returns:
But... StoreProducts List is null.
Use Dapper the way it works.
var listProduct = new Dictionary<string, Product>();
var listStoreProduct = new Dictionary<string, StoreProduct>();
using var connection = _connectionProvider.GetConnection();
var query = "SELECT * FROM StoreProduct AS A INNER JOIN Product AS B ON A.ProductId = B.ProductId WHERE A.StoreProductId = #StoreProductId and A.StoreId = #StoreId";
var result = connection.Query<Product, StoreProduct, Product>(query, (product, storeProduct) =>
{
if (!listProduct.TryGetValue(product.Id, out var entry))
{
entry = product;
listProduct.Add(entry.Id, entry);
}
if (storeProduct != null && !listStoreProduct.TryGetValue(storeProduct.Id, out var procInstance))
{
listStoreProduct.Add(procInstance.Id, procInstance);
entry.ProcessInstance.Add(procInstance);
}
return entry;
}, splitOn: "ProductId").Distinct().ToList();
I hope I could have helped you.
May be this could be accomplished using a trigger and an audit log table in SQL server. Or perhaps it could be accomplished by overriding the SaveChanges() method in Entity Framework. My concern is how to write the code to get it done and which one will be efficient. Can anybody help me?
If you log changes through the code, alongside data, you can add additional information to the audit log such as IP, user info, client info, ... and it is really helpful. The downside would be if someone changes data directly via the database you cannot find out what data has changed and the performance of logging data change by audit log table is better. If you just need to capture data change and don't need to find out who and from where data has changed choose the database approach.
Here is an implementation to capture data changes by EF Core:
public interface IAuditableEntity
{
}
public class AuditLogEntity
{
public int Id { get; set; }
public string UserName { get; set; }
public DateTime CreateDate { get; set; }
public ChangeType ChangeType { get; set; }
public string EntityId { get; set; }
public string EntityName { get; set; }
public IEnumerable<EntityPropertyChange> Changes { get; set; }
}
public class EntityPropertyChange
{
public string PropertyName { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
public enum ChangeType
{
Add = 1,
Edit = 2,
Remove = 3
}
In DbContext:
public class RegistryDbContext : DbContext
{
private readonly IHttpContextAccessor _contextAccessor;
public RegistryDbContext(DbContextOptions<RegistryDbContext> options, IHttpContextAccessor contextAccessor) :
base(options)
{
_contextAccessor = contextAccessor;
}
public DbSet<AuditLogEntity> AuditLogs { get; set; }
public override int SaveChanges()
{
CaptureChanges();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
CaptureChanges();
return await base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuditLogEntity>().Property(p => p.UserId).IsUnicode(false).HasMaxLength(36).IsRequired(false);
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityId).IsUnicode(false).HasMaxLength(36).IsRequired();
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityName).IsUnicode(false).HasMaxLength(256).IsRequired(false);
builder.Ignore(p => p.Changes);
modelBuilder.Entity<AuditLogEntity>()
.Property(p => p.Changes).IsUnicode().HasMaxLength(int.MaxValue).IsRequired(false)
.HasConversion(
changes => JsonConvert.SerializeObject(changes, Formatting.None, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}),
changes => JsonConvert.DeserializeObject<IList<EntityPropertyChange>>(changes, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}));
}
private void CaptureChanges()
{
var changes = ChangeTracker
.Entries<IAuditableEntity>()
.Where(e =>
e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.Select(GetAuditLogItems)
.ToList();
AuditLogs.AddRange(changes);
}
private AuditLogEntity GetAuditLogItems(EntityEntry entry)
{
var auditEntity = new AuditLogEntity
{
CreateDate = DateTime.Now,
EntityId = entry.Properties.FirstOrDefault(f => f.Metadata.IsPrimaryKey())?.CurrentValue?.ToString(),
EntityName = entry.Metadata.Name,
UserName = _contextAccessor?.HttpContext?.User?.Name.ToString(),
};
switch (entry.State)
{
case EntityState.Added:
auditEntity.ChangeType = ChangeType.Add;
auditEntity.Changes = GetChanges(entry.Properties, e => true).ToList();
foreach (var entityChange in auditEntity.Changes)
entityChange.OldValue = null;
break;
case EntityState.Modified:
auditEntity.ChangeType = ChangeType.Edit;
auditEntity.Changes = GetChanges(entry.Properties, e => e.IsModified).ToList();
break;
case EntityState.Deleted:
auditEntity.ChangeType = ChangeType.Remove;
break;
}
return auditEntity;
}
private IEnumerable<EntityPropertyChange> GetChanges(IEnumerable<PropertyEntry> properties,
Func<PropertyEntry, bool> predicate) => properties
.Where(predicate)
.Select(property =>
new EntityPropertyChange
{
PropertyName = property.Metadata.Name,
OldValue = property.OriginalValue?.ToString(),
NewValue = property.CurrentValue?.ToString()
});
}
I use IAuditableEntity (an empty interface) to mark entities that I want to capture changes.
public class CustomerEntity : IAuditableEntity
{
...
}
You can also use Audit.NET library to capture changes.
I've got a question relating the above mentioned topic.
------Using Dapper and Caliburn Micro-----
At the moment im building a mvvm wpf application which displays a list of orders. These orders need some ressources to be allocated so the workprocess can begin.
The list of orders provide a few buttons for each row (order) to start, pause, finish and to set the status of the order like "is material allocated".
Whats a good practise to save changes of the above steps made via the list to database?
When creating the order I simply pass the values via a buttonclick to a method in database access project.
For the moment lets just talk about the UIOrderModel and the property IsAllocated
Once the Button (the ugly beige one) is clicked the following method fires:
public void MatAllocated(UIOrderModel order) {
order.IsMatAllocated = "Y";
order.IsAllocated = true;
order.StatusFlag = MKDataWork.Library.Enums.StatusFlag.allocated;
OrdersBc.Refresh();
}
Since it's workig so far as it should be I'm on the question how to store the Information about the changed status of Allocation in my database. By way of example it would be kinda easy just to fire an update query (sp) in the method above.
The collection and the database should always have the same state of data.
The UiOrderModel:
public class UIOrderModel {
private UIOrderModel UIorder = null;
public int Code { get; set; }
public UIuserModel OrderedByEmp { get; set; }
public int OrderedByEmpPersId { get; set; }
public string OrderedByEmpName { get; set; }
public List<UIAllocationModel> AllocationList {get;set;}
public DateTime OrderDate { get; set; }
public UIDepartmentModel ForDepartment { get; set; }
public string DepartmentName { get; set; }
public DateTime ExpectedFinishDate { get; set; }
public string Project { get; set; }
public string Commission { get; set; }
public string IsMatAllocated { get; set; } = "N";
public bool IsAllocated { get; set; } = false;
public string Additions { get; set; }
public StatusFlag StatusFlag { get; set; }
public decimal? Quantity { get; set; }
public UnitOfMeasures Unit { get; set; }
public UIitemModel Item { get; set; }
public string ItemName { get; set; }
public string ItemCode { get; set; }
public DateTime FinishedDateTime { get; set; }
public UIuserModel FinishedByEmp { get; set; }
public UIOrderModel() { }
public UIOrderModel(Dictionary<string,object> entries) {
int i = 0;
this.UIorder = this;
this.Code = int.TryParse(entries["Code"].ToString(), out i) ? i : 0;
this.OrderedByEmp = (UIuserModel)entries["OrderedByEmp"];
this.OrderedByEmpPersId = ((UIuserModel)entries["OrderedByEmp"]).PersId;
this.ForDepartment = (UIDepartmentModel)entries["SelectedDepartment"];
this.DepartmentName = ((UIDepartmentModel)entries["SelectedDepartment"]).Bezeichnung;
this.ExpectedFinishDate = (DateTime)entries["ExpectedFinishDate"];
this.Quantity = (decimal?)entries["Quantity"];
this.Unit = (UnitOfMeasures)entries["SelectedUnitOfMeasures"];
this.Item = (UIitemModel)entries["Item"];
this.ItemName = ((UIitemModel)entries["Item"]).ItemName;
this.ItemCode = ((UIitemModel)entries["Item"]).ItemCode;
this.StatusFlag = (StatusFlag)entries["StatusFlag"];
this.Project = (string)entries["Project"];
this.Commission = (string)entries["Commission"];
this.Additions = (string)entries["Additions"];
}
public UIOrderModel(int code,string orderedByEmpName, int orderedByEmpPersId, string departmentName, DateTime expectedFinishDate,
decimal? quantity, UnitOfMeasures unit, string itemname, string itemcode, string project, string commission,
StatusFlag statusFlag, string additions)
{
this.UIorder = this;
this.Code = code;
this.OrderedByEmpPersId = orderedByEmpPersId;
this.OrderedByEmpName = orderedByEmpName;
this.DepartmentName = departmentName;
this.ExpectedFinishDate = expectedFinishDate;
this.Quantity = quantity;
this.Unit = unit;
this.ItemName = itemname;
this.StatusFlag = statusFlag;
this.Project = project;
this.Commission = commission;
this.Additions = additions;
}
public void SaveOrder() {
OrderModel result = (OrderModel)this;
result.SaveOrder();
}
public static explicit operator OrderModel(UIOrderModel uiOrder) {
return new OrderModel()
{
Code = uiOrder.Code,
OrderDate = uiOrder.OrderDate,
OrderedByEmp = (UserModel)uiOrder.OrderedByEmp,
OrderedByEmpName = $"{uiOrder.OrderedByEmp.FirstName} {uiOrder.OrderedByEmp.LastName}",
ExpectedFinishDate = uiOrder.ExpectedFinishDate,
ForDepartment = (DepartmentModel)uiOrder.ForDepartment,
AllocationList = uiOrder.AllocationList?.Select(am => (AllocationModel)am).ToList(),
IsMatAllocated = uiOrder.IsMatAllocated,
Quantity = uiOrder.Quantity,
Unit = uiOrder.Unit,
Item = (ItemModel)uiOrder.Item,
ItemCode = uiOrder.Item.ItemCode,
ItemName = uiOrder.Item.ItemName,
Project = uiOrder.Project,
Commission = uiOrder.Commission,
StatusFlag = uiOrder.StatusFlag,
Additions = uiOrder.Additions
};
}
public static explicit operator UIOrderModel(OrderModel order) {
return new UIOrderModel()
{
Code = order.Code,
OrderDate = order.OrderDate,
OrderedByEmp = (UIuserModel)order.OrderedByEmp,
OrderedByEmpName = $"{order.OrderedByEmp.FirstName} {order.OrderedByEmp.LastName}",
ExpectedFinishDate = order.ExpectedFinishDate,
ForDepartment = (UIDepartmentModel)order.ForDepartment,
AllocationList = order.AllocationList?.Select(am => (UIAllocationModel)am).ToList(),
IsMatAllocated = order.IsMatAllocated,
Quantity = order.Quantity,
Unit = order.Unit,
Item = (UIitemModel)order.Item,
ItemCode = order.ItemCode,
ItemName = order.ItemName,
Project = order.Project,
Commission = order.Commission,
StatusFlag = order.StatusFlag,
Additions = order.Additions
};
}
}
But whats the correct MVVM way to accomplish this in a proper manner?
Thanks for an advice!
Well I've been on vacation, returned and had some time and distance to the question above. Yesterday I chose a kinda simple way to solve it.
I've implemented the command pattern and passed the execution through my UI project to the data access. It's just one query for all kinds of statussteps (and the relating table) and updates of the whole order. Therefore I pass an enum value (action), orderNo and Id of the employee as parameter for the method / query.
I'm not completely sure if it's the best mvvm way but its working in a comfortable and fast way.
I am using Simple Membership and a UserProfile table that maintains UserId and UserName:
public partial class UserProfile
{
public UserProfile()
{
this.webpages_Roles = new List<webpages_Roles>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public virtual ICollection<webpages_Roles> webpages_Roles { get; set; }
}
With Entity Framework I am running the following which is inside my Context:
public partial class UowContext : DbContext
// code to set up DbSets here ...
public DbSet<Content> Contents { get; set; }
private void ApplyRules()
{
var r1 = new Random();
var r2 = new Random();
foreach (var entry in this.ChangeTracker.Entries()
.Where(
e => e.Entity is IAuditableTable &&
(e.State == EntityState.Added) ||
(e.State == EntityState.Modified)))
{
IAuditableTable e = (IAuditableTable)entry.Entity;
if (entry.State == EntityState.Added)
{
e.CreatedBy = // I want to put the integer value of UserId here
e.CreatedDate = DateTime.Now;
}
e.ModifiedBy = // I want to put the integer value of UserId here
e.ModifiedDate = DateTime.Now;
}
}
Here is the schema showing how user information is stored. Note that I store the integer UserId and not the UserName in the tables:
public abstract class AuditableTable : IAuditableTable
{
public virtual byte[] Version { get; set; }
public int CreatedBy { get; set; }
public DateTime CreatedDate { get; set; }
public int ModifiedBy { get; set; }
public DateTime ModifiedDate { get; set; }
}
Here's an example of a controller action that I use:
public HttpResponseMessage PostContent(Content content)
{
try
{
_uow.Contents.Add(content);
_uow.Commit();
var response = Request.CreateResponse<Content>(HttpStatusCode.Created, content);
return response;
}
catch (DbUpdateException ex)
{
return Request.CreateErrorResponse(HttpStatusCode.Conflict, ex);
}
}
I then have:
public class UowBase : IUow, IDisposable
{
public UowBase(IRepositoryProvider repositoryProvider)
{
CreateDbContext();
repositoryProvider.DbContext = DbContext;
RepositoryProvider = repositoryProvider;
}
public IRepository<Content> Contents { get { return GetStandardRepo<Content>(); } }
and:
public class GenericRepository<T> : IRepository<T> where T : class
{
public GenericRepository(DbContext dbContext)
{
if (dbContext == null)
throw new ArgumentNullException("An instance of DbContext is required to use this repository", "context");
DbContext = dbContext;
DbSet = DbContext.Set<T>();
}
public virtual void Add(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State != EntityState.Detached)
{
dbEntityEntry.State = EntityState.Added;
}
else
{
DbSet.Add(entity);
}
}
How can I determine the UserId from inside of my Context so I can populate the Id in my tables?
In Code you will have UserName with you through:
HttpContext.Current.User.Identity.Name
you can than query UserProfile table against that Name and get the UserId from there and than assign it to ModifiedBy attribute.
Make sure that you query UserProfile table outside the foreach loop :)
Im building a book library which handles Books and Journals.
I have a winform where the user enters some parameters to search on the library DB. i do not know which value he selects to insert.
For example:
public abstract class Item : IComparer, IEnumerable
{
public string Name { get; set; }
public string Publisher { get; set; }
public double Price { get; set; }
public double Discount { get; set; }
public DateTime ReleaseDate { get; set; }
}
this is my base class. when a user can insert to search by name (which translates into book name).
the UI has many options to search by, and basiclly includes almost all of the fields that a book contains.
What i need to do is filter out only the fields that the user inserted, pass them to my DAL so he can perform the search using a LINQ query on ENTITY framework mapped DB.
this is my search so far:
public List<Books> SelectBookFromDB(Item itemType)
{
BookVO book = itemType as BookVO;
var result = myEntities.Books.Where(x =>
x.Author == book.Author &&
x.Discount == book.Discount &&
x.Name == book.Name &&
x.Publisher == book.Publisher).ToList();
return result;
}
problem is, im searching for some values that may be null. that way, my searches always come up empty :(
i have no clue how to proceed. would love some help.
thanks!
Use more then one step to generate the query:
var result = myEntities.Books;
if(string.IsNullOrWhiteSpace(book.Author) == false)
{
result = result.Where(x => x.Author == book.Author);
}
if(string.IsNullOrWhiteSpace(book.Name) == false)
{
result = result.Where(x => x.Name == book.Name);
}
...And so on.
return result;
Change your Item class so that every type is properly nullable:
public abstract class Item : IComparer, IEnumerable
{
public string Name { get; set; }
public string Publisher { get; set; }
public double? Price { get; set; }
public double? Discount { get; set; }
public DateTime? ReleaseDate { get; set; }
}
This allows every property to clearly demarcate when it has a value and when it doesn't. What if someone wants to search for all books which are regular price (meaning Discount == 0)?
Then, you can simply add null checking to your results query:
var result = myEntities.Books.Where(x =>
(book.Author == null || x.Author == book.Author) &&
(book.Discount == null || x.Discount == book.Discount) &&
(book.Name == null || x.Name == book.Name) &&
(book.Publisher == null || x.Publisher == book.Publisher)).ToList();