I'm binding properties from an object like this:
ViewModel:
public class VieModel
{
public Model MyModel
{
get
{
return myModel;
}
set
{
myModel = value;
}
}
private Model myModel
}
Model:
public class Model
{
public string Name { get; set;}
public string Info { get; set;}
public string MoreInfo { get; set;}
}
View:
<TextBox Text="{Binding MyModel.Name, Mode=TwoWay, Mode=TwoWay, UpdateSourceTrigger=LostFocus }" />
<TextBox Text="{Binding MyModel.Info, Mode=TwoWay, UpdateSourceTrigger=LostFocus }" />
<TextBox Text="{Binding MyModel.MoreInfo, Mode=TwoWay, UpdateSourceTrigger=LostFocus }" />
Everything is working fine but when MyModel changes, I would like to check if any property has changed so I can ask the user to save first.
So my first thought was to add a boolean which turns to true on any change so I can check for that in the setter of MyModel. But currently I don't get a notification on MyModel when I change a Property even though it does in the background..
All in all i get it, cause MyModel does not really change.. but how do i detect any a change on any property?
Thanks in advance! :)
Answer:
To check if the current MyModel is the same as the old one u need to store copy of that object to be able to compare them as described by Bijington here
For that you can do it manually or use a Binary-Serializer so you can clone your mobject.
manually:
public class Model
{
public string Name { get; set;}
public string Info { get; set;}
public string MoreInfo { get; set;}
public static Model Clone(Model obj)
{
Model newModel = new Model();
newModel.Name = obj.Name;
newModel.Info = obj.Info;
newModel.MoreInfo = obj.MoreInfo;
return newModel;
}
}
BinaryFormatter.Serialize:
You need EVERY object used in you Model to have the [Serializable] attribute.
[Serializable]
public class Model
{
public string Name { get; set;}
public string Info { get; set;}
public string MoreInfo { get; set;}
public SecondModel SecondOne { get; set;}
public static Model Clone(Model obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
[Serializable]
public class SecondModel
{
public string SecondName { get; set;}
public string SecondInfo { get; set;}
public string SecondMoreInfo { get; set;}
}
I have slightly changed my approach from the initial comment. Instead of implementing IComparer the option I have gone with is to override the Equals method.
public class Model
{
public string Name { get; set; }
public string Info { get; set; }
public string MoreInfo { get; set; }
public override bool Equals(object obj)
{
if (obj is Model model)
{
return string.Equals(this.Name, model.Name, StringComparison.Ordinal);
// && Add your other property comparisons here...;
}
return false;
}
public override int GetHashCode()
{
// You really should provide your implementation here:
return base.GetHashCode();
}
}
Then once you have set up the Model class like the above you can do the checks inside your ViewModel class:
public class VieModel
{
private Model myModel;
public Model MyModel
{
get
{
return myModel;
}
set
{
if (myModel?.Equals(value) != true)
{
// The model has changed so you can perform what you want here...
myModel = value;
}
}
}
}
Related
I am using CQRS. I select my Entities IEnumerator from database and i want to map this to my Dto class.
My Dto class:
public class XCollectionDto
{
public IEnumerable<XReadDto> Entries { get; init; } = Enumerable.Empty<XReadDto>();
}
My mapper class:
public class XReadMapper : IEntityToDtoMapper<X, XCollectionDto>
{
public XCollectionDto Map(IEnumerable <X> source, XCollectionDto target)
{
//todo
Here i want to map source to target Entries list
}
}
How can i do that, without a for loop? I am not using AutoMaper, the mapping is manual
I think you could accompish your purpose with C# reflection
I created the two class for test:
public class somemodel
{
public int Id { get; set; }
public string Name { get; set; }
public List<int> Numlist { get; set; }
}
public class somemodelDTO
{
public int Id { get; set; }
public string SomeName { get; set; }
public List<int> Numlist { get; set; }
}
the method to bind properties of somemodelDTO which have the same name with properties of somemodel:
private static somemodelDTO GetMap<somemodel, somemodelDTO>(somemodel some)
{
somemodelDTO somemDTO = Activator.CreateInstance<somemodelDTO>();
var typesource = some.GetType();
var typedestination = typeof(somemodelDTO);
foreach(var sp in typesource.GetProperties())
{
foreach( var dp in typedestination.GetProperties())
{
if(sp.Name==dp.Name)
{
dp.SetValue(somemDTO, sp.GetValue(some, null), null);
}
}
}
return somemDTO;
}
The result:
I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}
I've got a simple model that looks like this:
public class ImageFile
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public ImageMeta Meta { get; set; }
public string FileName { get; set; }
public DateTime DateUploaded { get; set; }
public int Width { get; set; }
public int Height { get; set; }
}
Is there any way I can add some kind of "OnDelete" event hook so that whenever a model is deleted via db.Images.Remove(imageFile); or whatever other means, I can delete the associated file?
You can override the SaveChanges method of your context to hook up to delete entities.
public class GalleryContext : DbContext
{
public override int SaveChanges()
{
var deletedImages = ChangeTracker.Entries()
.Where(e => e.State == EntityState.Deleted && e.Entity is ImageFile)
.Select(e => e.Entity).Cast<ImageFile>();
foreach(var image in deletedImages)
{
// delete file here or call a method on image
}
return base.SaveChanges();
}
}
1. I have created Test Class which contain Static Class and Property.
namespace QSys.Data.Domain.DataSecurity
{
public static class TestData
{
public static string MyName { get; set; }
}
}
2. Customer Model class and Custom Validation
namespace QSys.Data.Domain
{
[Serializable()]
public class Customer
{
[Key]
public virtual int Id { get; set; }
[CustomValidation(typeof(CustomerRequiredRules), "IsCompanyNameEmpty")]
public virtual string CompanyName { get; set; }
public virtual string City { get; set; }
}
public class CustomerRequiredRules
{
public static ValidationResult IsCompanyNameEmpty(string CompanyName, ValidationContext context)
{
if (TestData.MyName == "Imdadhusen")
{
return new ValidationResult("Company name not allowed!", new string[] { "CompanyName" });
}
return ValidationResult.Success;
}
}
}
3. Setting value of Static class like
public class AdminHomeViewModel
{
public AdminHomeViewModel()
{
TestData.MyName = "Imdadhusen";
}
}
4. I click on submit button, my custom validation getting fired and here i couldn't able to get value of TestData.MyName. it will display Null instead of Imdadhusen.
Any Answer, Suggestion or Comment highly appreciated!
Thanks,
Imdadhusen
I worked through the Nerd Dinner application. In the Create action method they have the following code:
NerdIdentity nerd = (NerdIdentity)User.Identity;
dinner.HostedById = nerd.Name;
dinner.HostedBy = nerd.FriendlyName;
RSVP rsvp = new RSVP();
rsvp.AttendeeNameId = nerd.Name;
rsvp.AttendeeName = nerd.FriendlyName;
dinner.RSVPs.Add(rsvp);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
I am using Entity Framework 4.1 code first.
Here is my GrantApplication class:
public class GrantApplication
{
public int Id { get; set; }
// Other properties
public virtual ICollection<AuditEntry> AuditEntries { get; set; }
}
In my service layer I do the following, the same as what Nerd Dinner does it:
public void Insert(GrantApplication grantApplication)
{
// Add audit entry
grantApplication.AuditEntries.Add(new AuditEntry
{
NewValue = grantApplication.GrantApplicationStateId,
AuditDate = currentDateTime,
EmployeeNumber = submitterEmployeeNumber
});
// Insert the new grant application
grantApplicationRepository.Insert(grantApplication);
}
My AuditEntry class:
public class AuditEntry
{
public int Id { get; set; }
public int OldValue { get; set; }
public int NewValue { get; set; }
public DateTime AuditDate { get; set; }
public string EmployeeNumber { get; set; }
}
My context class:
public class HbfContext : DbContext
{
public DbSet<Bank> Banks { get; set; }
public DbSet<AccountType> AccountTypes { get; set; }
public DbSet<GrantApplication> GrantApplications { get; set; }
public DbSet<AuditEntry> AuditEntries { get; set; }
protected override void OnModelCreating(DbModelBuilder dbModelBuilder)
{
}
}
I get an error that grantApplication.AuditEntries is null so it can't add the audit entry object. Why is mine null, but dinner.RSVPs is not null when it tries to add the RSVP obkect? How would I fix it?
Do I need to add AuditEntries to HbfContext? I mean I'm not going to use it on it's own. It will only be used when a GrantApplication is edited.
UPDATE
I must be using an older version of Nerd Dinner, but this is what my Create looks like:
[HttpPost, Authorize]
public ActionResult Create(Dinner dinner)
{
if (ModelState.IsValid)
{
NerdIdentity nerd = (NerdIdentity)User.Identity;
dinner.HostedById = nerd.Name;
dinner.HostedBy = nerd.FriendlyName;
RSVP rsvp = new RSVP();
rsvp.AttendeeNameId = nerd.Name;
rsvp.AttendeeName = nerd.FriendlyName;
dinner.RSVPs.Add(rsvp);
dinnerRepository.Add(dinner);
dinnerRepository.Save();
return RedirectToAction("Details", new { id=dinner.DinnerID });
}
return View(dinner);
}
Um, because the NerdDinner DinnersController.Create includes a line of code which you didn't show?
dinner.RSVPs = new List<RSVP>(); // why is this not in your example?
dinner.RSVPs.Add(rsvp);
Do I need to add AuditEntries to HbfContext?
Yes, you do. Well, you have to add them to your EF model in some way. That's one way to do it. You might be able to do it with code in OnModelCreating as well.