Currently, I'm working on an Angular (front-end) C# (back-end) application. This application uses Entity Framework code first.
I'm going to work with an Activity Log, basically to save changes inside an app Profile section, so I think about the following model for that:
public partial class ProfileActivity : BaseAuditModel, IEntity
{
public string Description { get; set; }
public int ProfileId { get; set; }
public virtual Profile Profile { get; set; }
public int ActivityTypeId { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
Well, now on update endpoint (controller), I receive data from the front end as:
[Authorize]
[HttpPost("AddProfile")]
[CleanCache]
public async Task<ActionResult<Profile>> AddProfile(Profile profile)
{
var user = await this.GetUser();
var model = await _profileService.EditProfile(profile, user);
if (model != null) return Ok(model);
return NotFound();
}
My question is right here. How can I compare the received model (with changes) with the current Profile (with no changes yet)? I can get the model without changes by doing a get operation before saving the changes as:
var currentProfile = GetProfile(profile.Id);
But now, how can I identify all the changes between received profile model and the currentProfile?
Profile Model:
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public int VettingStatusId { get; set; }
public int? RecruiterId { get; set; }
etc..
So, at the end of the date, I can insert into my new table ProfileActivity all the changes where we found the differences.
From your description, I think you wanna compare two objects and put the different properties into a list mdoel. So you can refer to this simple demo:
NuGet Package:
Install-Package ObjectsComparer
Model:
public class ComparedModel
{
public string Description { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
Demo:
//current model
var a1 = new ClassA()
{
Id = 1,
FirstName = "AAA",
Age = 23,
LastName = "BB"
};
//new model from frontend
var a2 = new ClassA()
{
Id = 1,
FirstName = "AAA",
Age = 16,
LastName = "BBVV"
};
//receive the different properties
List<ComparedModel> t = new List<ComparedModel>();
var comparer = new ObjectsComparer.Comparer<ClassA>();
IEnumerable<Difference> differences;
var isEqual = comparer.Compare(a1, a2, out differences);
if (!isEqual)
{
foreach (var item in differences)
{
t.Add(new ComparedModel()
{
Description = item.MemberPath,
OldValue = item.Value1,
NewValue = item.Value2
});
}
}
You can see the propertiy name, old value and new value have been inserted into the list model.
=======Update======
You can use reflection, for example:
public static void GetChanges<T>(T a, T b) where T : class {
foreach (var prop in typeof(T).GetProperties()) {
if(prop.GetValue(a) != prop.GetValue(b))
System.Diagnostics.Debug.Print(prop.Name);
}
}
Related
I want to update my existing entities that consist a parent entity and two child entities.
Previously I have utilized view model to create data that include a parent class and two child class, however I could not use view model as parameter in update because it requires an integer parameter to know which Id I should update.
Relationship diagram:
Model Relationship
Model classes:
public class Requisition
{
[Key]
public int requisitionId { get; set; }
public string Branch { get; set; }
public List<TravelAttachment> AllFiles { get; set; }
public List<Itinerary> Itineraries { get; set; } = new List<Itinerary>();
}
public class TravelAttachment
{
[Key]
public int AttachId { get; set; }
public string FilePath { get; set; }
public int? requisitionId { get; set; }
}
public class Itinerary
{
[Key]
public int ItineraryId { get; set; }
public string Departure { get; set; } = "";
public string Arrival { get; set; } = "";
public int? requisitionId { get; set; }
}
View model:
public class RequisitionViewModel
{
public int requisitionId { get; set; }
public string Branch { get; set; } = "";
public List<IFormFile> File { get; set; }
public List<Itinerary> Itineraries { get; set; } = new List<Itinerary>();
}
What I have tried is to create a view model object in my controller, and the outcome is telling me one of my variable in view model object get returned with null value.
Controller
public IActionResult UpdateRequisition(int id)
{
if (ModelState.IsValid)
{
var requisition = _context.Requisitions
.Include(r => r.Itineraries)
.Include(r => r.AllFiles)
.AsNoTracking()
.FirstOrDefault(m => m.requisitionId == id);
string uniqueFileName = null;
// have null object here
RequisitionViewModel model = new RequisitionViewModel();
List<TravelAttachment> attachments = new List<TravelAttachment>();
//problem that inform my model.File is null
foreach (IFormFile file in model.File)
{
string uploadsFolder = Path.Combine(_webHostEnvironment.WebRootPath, "dist/files");
uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName;
string filePath = Path.Combine(uploadsFolder, uniqueFileName);
file.CopyTo(new FileStream(filePath, FileMode.Create));
attachments.Add(new TravelAttachment { FilePath = uniqueFileName });
}
// using automapper to link viewmodel with model
var requisitionApplied = _mapper.Map<Requisition>(model);
requisitionApplied.AllFiles = attachments;
requisitionApplied.Itineraries = requisition.Itineraries;
_context.Requisitions.Update(requisitionApplied);
_context.SaveChanges();
return RedirectToAction("Index");
}
return View(id);
}
In this case, I am not able to upload new files in this action, what should I do to make sure user can upload new files?
Please ask me for additional information if you need more elaboration. I am appreciated for your kind support!
I have started looking into TPL Dataflow as a solution to a processing workflow.
The gist of the processing workflow is to read in input messages from multiple tables and create four reflecting objects from them and persist them to four other tables, so each input message should result in four new messages getting created.
I cannot identify one of the predefined blocks that can help with the creation of the four objects, at first TransformManyBlock seems to be what I am looking for but it returns multiple objects of the same type where I will have four types.
Example of the Problem
We have two tables containing Employee details from two legacy systems, their entities look like this
public partial class EmployeeTblA
{
public int Id { get; set; }
public int System { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int Age { get; set; }
public int Number { get; set; }
public string Street { get; set; }
public string PostCode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeTblB
{
public int Id { get; set; }
public int System { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
We want to take the data from the two systems and put the data into our shiny new system, to do this we need to convert the entities from the old system to the entities used in the new system. First we convert the entities from the old system to a base class which look like this
public class BaseEmployee
{
public int Id { get; set; }
public int System { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
}
We then want to create three new objects from the base class that represent the entities of the new system which looks like this
public partial class EmployeeName
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public string Name { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeAge
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public int Age { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
public partial class EmployeeAddress
{
public int Id { get; set; }
public int System { get; set; }
public int LegacyId { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public virtual EmployeeSystem SystemNavigation { get; set; }
}
The rough flow of my TPL for the above example
Read data from tables in DB into a TranformBlock<Employee,BaseEmployee> converting to a common object, this is down twice for each of the legacy systems
Each TranformBlock<Employee, BaseEmployee> is linked to a BatchBlock to group all the input flows.
BatchBlock is linked to a Block<BaseEmployee, ...> which will take the input and create two new objects from the input data, EmployeeName and EmployeeAge.
The Block<BaseEmployee, ...> will then be linked to Action block and Action which take save them to their respective table's in the DB
I know I can create a custom block but I cannot figure out how I could use it to provide the output to four separate linked ActionBlock using dataflow, can someone please point me in the right direction?
The Broadcast block was the component I ended up going with, I used it to broadcast the BaseEmployee object to other output streams splitting out the reflecting objects I needed to create.
Full pipeline same below
_transEmployeeA = new TransformBlock<EmployeeTblA, BaseMsg>((input) =>
{
return new BaseMsg()
{
Id = input.Id,
System = input.System,
Name = string.Concat(input.Forename, " ", input.Surname),
Age = input.Age,
Address = string.Concat(input.Number, " ", input.Street),
Postcode = input.PostCode
};
});
_transEmployeeB = new TransformBlock<EmployeeTblB, BaseMsg>((input) =>
{
return new BaseMsg()
{
Id = input.Id,
System = input.System,
Name = input.Name,
Age = input.Age,
Address = input.Address,
Postcode = input.Postcode
};
});
_broadcastBaseMsg = new BroadcastBlock<BaseMsg>(null);
_transEmployeeName = new TransformBlock<BaseMsg, EmployeeName>((baseMsg) =>
{
return new EmployeeName()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Name = baseMsg.Name
};
});
_transEmployeeAge = new TransformBlock<BaseMsg, EmployeeAge>((baseMsg) =>
{
return new EmployeeAge()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Age = baseMsg.Age
};
});
_transEmployeeAddress = new TransformBlock<BaseMsg, EmployeeAddress>((baseMsg) =>
{
return new EmployeeAddress()
{
System = baseMsg.System,
LegacyId = baseMsg.Id,
Address = baseMsg.Address,
Postcode = baseMsg.Postcode
};
});
_bufferEmployeeName = new BufferBlock<EmployeeName>();
_bufferEmployeeAge = new BufferBlock<EmployeeAge>();
_bufferEmployeeAddress = new BufferBlock<EmployeeAddress>();
_actionEmployeeName = new ActionBlock<EmployeeName>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeNames.Add(output);
cxt.SaveChanges();
}
});
_actionEmployeeAge = new ActionBlock<EmployeeAge>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeAges.Add(output);
cxt.SaveChanges();
}
});
_actionEmployeeAddress = new ActionBlock<EmployeeAddress>((output) =>
{
using (var cxt = new SandboxContext())
{
cxt.EmployeeAddresses.Add(output);
cxt.SaveChanges();
}
});
var linkOpts = new DataflowLinkOptions()
{
PropagateCompletion = true
};
// Transform Employees and pass to Batch
_transEmployeeA.LinkTo(_broadcastBaseMsg, linkOpts);
_transEmployeeB.LinkTo(_broadcastBaseMsg, linkOpts);
// Transform Broadcast to respective outputs
_broadcastBaseMsg.LinkTo(_transEmployeeName, linkOpts);
_broadcastBaseMsg.LinkTo(_transEmployeeAge, linkOpts);
_broadcastBaseMsg.LinkTo(_transEmployeeAddress, linkOpts);
// Add outputs to Buffer
_transEmployeeName.LinkTo(_bufferEmployeeName, linkOpts);
_transEmployeeAge.LinkTo(_bufferEmployeeAge, linkOpts);
_transEmployeeAddress.LinkTo(_bufferEmployeeAddress, linkOpts);
// Persist outputs to DB
_bufferEmployeeName.LinkTo(_actionEmployeeName, linkOpts);
_bufferEmployeeAge.LinkTo(_actionEmployeeAge, linkOpts);
_bufferEmployeeAddress.LinkTo(_actionEmployeeAddress, linkOpts);
Additionally comments from #TheodorZoulias helped to simply my usage of TPL dataflow for this particular dataflow.
i have table of news , and i need convert to model .
like this :
i need pass to view NewsModel and i have table News .
i convert that :
public ActionResult EditNews(int NewsID)
{
NewsModel model = new NewsModel();
var editservice = _NewsSerivce.NewsByID(NewsID);
model.NewsID = editservice.NewsID;
model.NewsHeader = editservice.NewsHeader;
model.NewsTitle = editservice.NewsTitle;
model.NewsText = editservice.NewsText;
model.NewsDefaultFile = editservice.NewsDefaultFile;
model.CatID = editservice.CatID;
model.SubCatID = editservice.SubCatID;
DropDownCategory(model);
return View("Edit",model);
}
it work but i need use best way for convert .
what best way ?
You can use this nuget:
http://automapper.org/
Documentation is here:
https://github.com/AutoMapper/AutoMapper/wiki/Getting-started
I dont know how your class looks so i make my class:
public class TestClass
{
public int NewsID { get; set; }
public string NewsHeader { get; set; }
public string NewsTitle { get; set; }
public string NewsText { get; set; }
public string NewsDefaultFile { get; set; }
public int CatID { get; set; }
public int SubCatID { get; set; }
}
and my mapping class:
public class NewTestClass
{
public int NewsID { get; set; }
public string NewsHeader { get; set; }
public string NewsTitle { get; set; }
public string NewsText { get; set; }
public string NewsDefaultFile { get; set; }
public int CatID { get; set; }
public int SubCatID { get; set; }
}
i have map this class like this:
TestClass tc = new TestClass { CatID = 1, NewsID = 1, SubCatID = 1, NewsDefaultFile = "test1", NewsHeader = "test2", NewsText = "test3", NewsTitle = "test4" };
Mapper.Initialize(cfg => cfg.CreateMap<TestClass, NewTestClass>());
var config = new MapperConfiguration(cfg => cfg.CreateMap<TestClass, NewTestClass>());
var mapper = new Mapper(config);
NewTestClass ntx = Mapper.Map<NewTestClass>(tc);
Console.WriteLine(ntx.NewsID);
in my viewpoint, this link is the best of the method to Entity Framework Code First to an Existing Database.
In the visual studio go to Project -> Add New Item…
Select Data from the left menu and then ADO.NET Entity Data Model,
Enter BloggingContext as the name and click OK
This launches the Entity Data Model Wizard
Select Code First from Database and click Next
WizardOneCFE
Select the connection to the database you created in the first section and click Next
WizardTwoCFE
Click the checkbox next to Tables to import all tables and click Finish
WizardThreeCFE
I have a viewmodel which needs data from two models person and address:
Models:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Gender { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public int Zip { get; set; }
public int PersonId {get; set; }
}
The Viewmodel is as such
public class PersonAddViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Street { get; set; }
}
I have tried several ways to get data into the viewmodel and pass it to the view. There will be multiple records returned to display.
My latest method is populating the view model as such:
private AppContexts db = new AppContexts();
public ActionResult ListPeople()
{
var model = new PersonAddViewModel();
var people = db.Persons;
foreach(Person p in people)
{
Address address = db.Addresses.SingleOrDefault(a => a.PersonId == p.Id)
model.Id = p.Id;
model.Name = p.Name;
model.Street = address.Street;
}
return View(model.ToList());
}
I get an error on the Address address = db... line of "EntityCommandExecutionException was unhandled by user code.
How can you populate a view model with multiple records and pass to a view?
Final Solution:
private AppContexts db = new AppContexts();
private AppContexts dbt = new AppContexts();
public ActionResult ListPeople()
{
List<PersonAddViewModel> list = new List<PersonAddViewModel>();
var people = db.Persons;
foreach(Person p in people)
{
PersonAddViewModel model = new PersonAddViewModel();
Address address = dbt.Addresses.SingleOrDefault(a => a.PersonId == p.Id)
model.Id = p.Id;
model.Name = p.Name;
model.Street = address.Street;
}
return View(list);
}
First, EntityCommandExecutionException errors indicates an error in the definition of your entity context, or the entities themselves. This is throwing an exception because it's found the database to be different from the way you told it that it should be. You need to figure out that problem.
Second, regarding the proper way to do this, the code you've shown should work if your context were correctly configured. But, a better way would be to use Navigational properties, so long as you want to get all related records and not specify other Where clause parameters. A navigational property might look like this:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public int Gender { get; set; }
public virtual Address Address { get; set; }
// or possibly, if you want more than one address per person
public virtual ICollection<Address> Addresses { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public int Zip { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
Then you would simply say:
public ActionResult ListPeople()
{
var model = (from p in db.Persons // .Includes("Addresses") here?
select new PersonAddViewModel() {
Id = p.Id,
Name = p.Name,
Street = p.Address.Street,
// or if collection
Street2 = p.Addresses.Select(a => a.Street).FirstOrDefault()
});
return View(model.ToList());
}
For displaying lists of objects, you could use a generic view model that has a generic list:
public class GenericViewModel<T>
{
public List<T> Results { get; set; }
public GenericViewModel()
{
this.Results = new List<T>();
}
}
Have a controller action that returns, say all people from your database:
[HttpGet]
public ActionResult GetAllPeople(GenericViewModel<People> viewModel)
{
var query = (from x in db.People select x); // Select all people
viewModel.Results = query.ToList();
return View("_MyView", viewModel);
}
Then make your view strongly typed, taking in your generic view model:
#model NameSpace.ViewModels.GenericViewModel<NameSpace.Models.People>
I'm using EF4.1 code first to create a simple database app with SQL CE 4 backend. I have a Product class and a CallItem class defined as so:
class CallItem
{
public int id { get; set; }
public float discount { get; set; }
public virtual Product Product { get; set; }
}
class Product
{
public int id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool isWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
edit - When I am creating a collection of CallItems using a LINQ query, I cannot access the attributes of the Product attached to each CallItem, eg
var callItems = from ci in context.CallItems select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
Interrogating the database shows that Productid in CallItems is being populated. However, the following line generates a NullReferenceException during run time:
nrs.prodCode = callitem.Product.Code;
Because callitem.Product is evaluating to null. Is this something to do with lazy loading and if so how can I resolve the issue?
RunSheet is another class, nrs is an instance whose attribute 'prodCode' I want to populate with the CallItem's Product's code.
Thanks!
From that code what you've showed it should work. Have you tried explicit loading?
var callItems = from ci in context.CallItems.Include(c => c.Product) select ci;
foreach(CallItem callItem in callItems)
{
RunSheet nrs = new RunSheet();
nrs.prodCode = callitem.Product.Code;
}
public class CallItem
{
public int Id { get; set; }
public float Discount { get; set; }
public virtual Product Product { get; set; }
}
public class Product
{
public int Id { get; set; }
public decimal BaseCost { get; set; }
public int UnitSize { get; set; }
public bool IsWasteOil { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Ingredients { get; set; }
}
using (var context = new StackOverFlowContext())
{
var p = new Product
{
Id = 1,
BaseCost = 200,
Code = "Hola",
Description = "Soe description",
Ingredients = "Some ingredients",
IsWasteOil = true,
Name = "My Product",
UnitSize = 10
};
var item = new CallItem
{
Id = 101,
Discount = 10,
Product = p
};
context.CallItems.Add(item);
context.SaveChanges();
var result = from temp in context.CallItems
select temp;
Console.WriteLine("CallItem Id"+result.First().Id);
Console.WriteLine("ProductId"+result.First().Product.Id);
}
I wrote the above code with the following output
CallItemId 1
ProductId 1
The sql Profiler showed this
SELECT TOP (1)
[c].[Id] AS [Id],
[c].[Discount] AS [Discount],
[c].[Product_Id] AS [Product_Id]
FROM [dbo].[CallItems] AS [c]
It was too long for a comment ,so i put it here .