EF tracking changes in database - c#

im using Entity Framework6 in my solution, and now i need to track changes in database but cant figure out how to do it.
Is there a way to track changes in database using Entity Framwork?
update:
I've found this solution, its using SqlDependency in the way that allows to use it With Entity framework.
http://www.codeproject.com/Articles/233770/AutoRefresh-Entity-Framework-data-using-SQL-Server

I've found this solution, its using SqlDependency in the way that allows to use it With Entity framework.
http://www.codeproject.com/Articles/233770/AutoRefresh-Entity-Framework-data-using-SQL-Server

You can overwrite the SaveChanges() method to track the changes. Here's some example out of one of our applications:
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
var entries = ChangeTracker.Entries<IAuditable>();
if (entries != null)
{
foreach (DbEntityEntry<IAuditable> entry in entries)
{
switch (entry.State)
{
case EntityState.Added:
entry.Entity.SystemFields = new SystemFields
{
SysActivityCode = ActivityCode,
SysCreationUser = UserId,
SysCreationDate = DateTime.UtcNow
};
break;
case EntityState.Modified:
entry.Entity.SystemFields.SysModificationDate = DateTime.UtcNow;
entry.Entity.SystemFields.SysModificationUser = UserId;
entry.Entity.SystemFields.SysActivityCode = ActivityCode;
break;
}
}
}
return base.SaveChanges();
}
where SystemFields is a ComplexType added to all entries which implement the IAuditable interface:
public interface IAuditable
{
/// <summary>
/// System fields who contain change and status information
/// </summary>
SystemFields SystemFields { get; set; }
}
[ComplexType]
public class SystemFields
{
/// <summary>
/// When has this entry be created
/// </summary>
[Required]
public DateTime SysCreationDate { get; set; }
/// <summary>
/// Who has created the entry
/// </summary>
[Required]
public string SysCreationUser { get; set; }
/// <summary>
/// When has this entry been modified
/// </summary>
public DateTime? SysModificationDate { get; set; }
/// <summary>
/// Who has updated the entry
/// </summary>
[CanBeNull]
public string SysModificationUser { get; set; }
/// <summary>
/// Which activity has created/changed this entry
/// </summary>
[Required]
[StringLength(32)]
public string SysActivityCode { get; set; }
}

Related

EF7 - Properties object from entity class are not being filled

Good night!
I am playing with EF7 to learn about it.
I've created the next classes:
public class Lawsuit
{
/// <summary>
/// Lawsuit identificator.
/// </summary>
public Guid LawsuitId { get; set; }
/// <summary>
/// Date time asigned where lawsuit was created.
/// </summary>
public DateTime DateTimeCreated { get; set; }
/// <summary>
/// First part envolved signature object.
/// </summary>
public virtual Signature? FirstPartyEnvolved { get; set; }
/// <summary>
/// First part envolved signature object.
/// </summary>
public virtual Signature? SecondPartyEnvolved { get; set; }
/// <summary>
/// Winner position indicator
/// </summary>
public WinnerPosition? WinnerPosition { get; set; }
}
public class Signature
{
/// <summary>
/// Id from signature.
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// String which saves text received from contract signatures.
/// </summary>
public string? Signatures { get; set; }
}
At insert, the data is created correctly at database (SQL Server), but when I try to obtain the data, FirstPartyEnvolved and SecondPartyEnvolved attributes are empty.
Example of response:
[
{
"lawsuitId": "6ece06da-d5f9-40e7-a7a3-2816618e7869",
"dateTimeCreated": "2022-12-12T22:05:24.4717517",
"firstPartyEnvolved": null,
"secondPartyEnvolved": null,
"winnerPosition": 1
}
]
I was checking if the DTO object was not parsing well the data but the problem seems to be at the Entity object
public Task Handle()
{
var Lawsuites_repo = LawsuitRepository.GetAllLawsuit();
var Lawsuites = Lawsuites_repo.Select(x =>
new LawsuitDTO
{
LawsuitId = x.LawsuitId,
DateTimeCreated = x.DateTimeCreated,
WinnerPosition = x.WinnerPosition ?? 0,
FirstPartyEnvolved = x.FirstPartyEnvolved,
SecondPartyEnvolved = x.SecondPartyEnvolved
});
GetAllLawsuitsOutputPort.Handle(Lawsuites);
return Task.CompletedTask;
}
I've tried to changed the Signatures objects for GUID and I am receiving well this data. Seems a problem parsing the object from the EF context to the Entity class.
You haven't detailed what is in your GetAllLawsuit() method, but EF does not eager load related entities by default, so you are likely just ToList()'ing the content, then the collection will be loaded from the database with null references for the Signatures
If you remove the call to GetAllLawsuit() and use the Select projection directly from the DbContext, then it will work.
You can also force EF to eager load your related entities by using the Include extension method in your GetAllLawsuit method.

Cutting down LINQ Query time

Good morning. I'm trying to cut down the time on a LINQ Query. During the execution of the block of code, against large datasets it takes about 30-40 seconds to complete, which is way too long.
foreach (var patientVisitId in patientVisitIds)
{
var firstVisit = visitsWithBills.First(vb => vb.Visit.PatientVisitId == patientVisitId).Visit;
firstVisit.Bills = (from visitBill in visitsWithBills
where visitBill.Visit.PatientVisitId == patientVisitId
select visitBill.Bill).ToList();
visitTOs.Add(firstVisit);
}
I've tried replacing the == within the where statement with .contains which I read is supposed to be quicker, that almost doubles the execution time.
foreach (var patientVisitId in patientVisitIds)
{
var firstVisit = visitsWithBills.First(vb => vb.Visit.PatientVisitId == patientVisitId).Visit;
firstVisit.Bills = (from visitBill in visitsWithBills
where visitBill.Visit.PatientVisitId.Contains(patientVisitId)
select visitBill.Bill).ToList();
visitTOs.Add(firstVisit);
}
Here's the Object that firstVisit represents.
public class VisitTO
{
#region { Instance properties }
/// <summary>
/// Gets or sets the bed/room number for the visit
/// </summary>
public string Bed { get; set; }
/// <summary>
/// Gets or sets the bills for the visit
/// </summary>
public List<BillTO> Bills { get; set; }
/// <summary>
/// Gets or sets the date of admission for the visit
/// </summary>
public DateTime DateOfAdmission { get; set; }
/// <summary>
/// Gets or sets the primary diagnosis for the patient
/// </summary>
public string DX1 { get; set; }
/// <summary>
/// Gets or sets the secondary diagnosis for the patient
/// </summary>
public string DX2 { get; set; }
/// <summary>
/// Gets or sets the tertiary diagnosis for the patient
/// </summary>
public string DX3 { get; set; }
/// <summary>
/// Gets or sets the quaternary diagnosis for the patient
/// </summary>
public string DX4 { get; set; }
/// <summary>
/// Gets or sets the quinary diagnosis for the patient
/// </summary>
public string DX5 { get; set; }
/// <summary>
/// Gets or sets the senary diagnosis for the patient
/// </summary>
public string DX6 { get; set; }
/// <summary>
/// Gets or sets whether the patient has been discharged
/// </summary>
public bool IsDischarged { get; set; }
/// <summary>
/// Gets or sets the patient's full name
/// </summary>
public string PatientName { get; set; }
/// <summary>
/// Gets or sets the patient's current visit ID
/// </summary>
public string PatientVisitId { get; set; }
/// <summary>
/// Gets or sets the patient's current visit ID
/// </summary>
public string PatientId { get; set; }
/// <summary>
/// Gets or sets the name of the patient's primary care physician
/// </summary>
public string PrimaryCarePhysician { get; set; }
/// <summary>
/// Gets or sets the hosting site
/// </summary>
public string Site { get; set; }
/// <summary>
/// Gets or sets the team assignment
/// </summary>
public string Team { get; set; }
#endregion { Instance properties }
}
Here's BillTO object.
public class BillTO
{
#region { Public instance properties }
/// <summary>
/// Gets or sets the bill's date
/// </summary>
public DateTime Date { get; set; }
/// <summary>
/// Gets or sets the name for the doctor on the bill
/// </summary>
public string DoctorName { get; set; }
/// <summary>
/// Gets or sets the bill's type
/// </summary>
public string Type { get; set; }
/// <summary>
/// Gets or sets the encounter for this bill
/// </summary>
public string Encounter { get; set; }
/// <summary>
/// Gets or sets the CPT Code
/// </summary>
public string CptCode { get; set; }
#endregion { Public instance properties }
}
Database Query to the get the list.
private static readonly Func<MDataContext, IQueryable<VisitBillTO>> ActiveVisitsWithBillsQuery =
CompiledQuery.Compile<MContext, IQueryable<VisitBillTO>>(
dbContext => (
from visit in dbContext.AV
join bill in dbContext.ABills on visit.PatientVisitId equals bill.PatientVisitId
where (visit.BFlag == null || visit.BFlag != "BI")
orderby visit.PatientVisitId
select new VisitBillTO
{
Bill = new BillTO
{
Date = bill.Date.GetValueOrDefault(DateTime.Today),
DoctorName = bill.DoctorName,
Type = bill.Type,
Encounter = bill.Encounter,
CptCode = bill.CptCode
},
Visit = new VisitTO
{
Bed = visit.Bed,
DateOfAdmission = visit.DateOfAdmission.GetValueOrDefault(DateTime.Today),
DX1 = visit.DX1,
DX2 = visit.DX2,
DX3 = visit.DX3,
DX4 = visit.DX4,
DX5 = visit.DX5,
DX6 = visit.DX6,
IsDischarged = (visit.IsDischargedCode != null && visit.IsDischargedCode == "Y"),
PatientName = (visit.PatientFullName ?? visit.PatientLastName + ", " + visit.PatientFirstName),
PatientVisitId = visit.PatientVisitId,
PatientId = visit.PatientID,
PrimaryCarePhysician = visit.PrimaryCarePhysician,
Site = visit.Site,
Team = visit.Team
}
}
));
As I expected, you can do this far more efficiently in one query:
from visit in dbContext.AV
where (visit.BFlag == null || visit.BFlag != "BI")
&& patientVisitIds.Contains(visit.PatientVisitId)
orderby visit.PatientVisitId
select new VisitBillTO
{
Bed = visit.Bed,
...
Team = visit.Team,
Bills = (from bill
in visit.Bills
select new BillTO
{
Date = bill.Date.GetValueOrDefault(DateTime.Today),
DoctorName = bill.DoctorName,
Type = bill.Type,
Encounter = bill.Encounter,
CptCode = bill.CptCode
})
}
Now the database does all the heavy lifting of combining the objects. Everything is shaped as you want in one go.
Note that I assume the navigation property visit.Bills to exist. These properties normally exist in LINQ-to-SQL contexts, but in the designer for some reason they're always collapsed by default, so people tend to overlook them. If for some reason the property isn't there you can replace...
Bills = (from bill in visit.Bills
by...
Bills = (dbContext.ABills where visit.PatientVisitId == bill.PatientVisitId

C# Automapper 6.0.2 Keeps throwing StackOverflowException

Initializing the mapper
public static class MapperConfig
{
public const int MAX_MAPPING_DEPTH = 2;
private static MapperConfiguration _config;
private static IMapper _mapper;
public static IMapper Mapper => _mapper ?? (_mapper = GetConfig().CreateMapper());
public static MapperConfiguration GetConfig()
{
var assembly = Assembly.GetExecutingAssembly();
if (_config != null)
return _config;
_config = new MapperConfiguration(cfg =>
{
cfg.AddProfiles(assembly);
cfg.ForAllMaps(ConfigTypeMapping);
});
_config.AssertConfigurationIsValid();
return _config;
}
public static void ConfigTypeMapping(TypeMap map, IMappingExpression expression)
{
map.ShouldCheckForValid();
expression.PreserveReferences();
expression.MaxDepth(MAX_MAPPING_DEPTH);
//expression.ForAllMembers(m => m.UseDestinationValue());
}
}
The main entity
[ResourceKey("MEDIA_ITEM")]
public class MediaItem : SocialEntity
{
/// <summary>
/// User-defined media item <see cref="Title"/> or its original file name.
/// </summary>
public string Title { get; set; }
/// <summary>
/// The original <see cref="FileName"/>.
/// </summary>
public string FileName { get; set; }
/// <summary>
/// The <see cref="FileGuid"/> that refers to the CDN file entry.
/// </summary>
public Guid FileGuid { get; set; }
/// <summary>
/// The purpose / group (aka <see cref="Type"/>) for this <see cref="MediaItem"/>.
/// </summary>
public FileType Type { get; set; }
/// <summary>
/// Allows limiting retrieval of this <see cref="MediaItem"/> depending on a specific <see cref="PrivacyLevel"/>.
/// </summary>
public PrivacyLevel PrivacyLevel { get; set; }
/// <summary>
/// The <see cref="Source"/> for the Content / File of this <see cref="MediaItem"/>.
/// </summary>
public virtual Url Source { get; set; }
public Guid? SourceGuid { get; set; }
/// <summary>
/// The <see cref="Profile"/> entity that this media file is bound to.
/// </summary>
public virtual Profile Profile { get; set; }
public Guid? ProfileGuid { get; set; }
/// <summary>
/// The <see cref="Article"/> entity that this media file is bound to.
/// </summary>
public virtual Article Article { get; set; }
public Guid? ArticleGuid { get; set; }
/// <summary>
/// The <see cref="Communication.Comment"/> entity that this media file is bound to.
/// </summary>
public virtual Comment Comment { get; set; }
public Guid? CommentGuid { get; set; }
/// <summary>
/// The <see cref="Theme"/> entity that this media file is bound to.
/// </summary>
public virtual Theme Theme { get; set; }
public Guid? ThemeGuid { get; set; }
}
As you can see, the entity inherits from the SocialEntity that holds a bunch of default collections. The SocialEntity then inherits the base Entity class with the Id, user, date created etc.
Base class mapping
CreateMap<SocialEntity, SocialDomainModel>()
.IncludeBase<Entity, DomainModel>()
.ReverseMap()
.IncludeBase<DomainModel, Entity>();
CreateMap<Entity, DomainModel>()
.IncludeBase<global::Data.Pattern.Entity.Entity, DomainModel>()
.ReverseMap()
.IncludeBase<DomainModel, global::Data.Pattern.Entity.Entity>();
CreateMap<global::Data.Pattern.Entity.Entity, DomainModel>()
.ForMember(dm => dm.Creator, mo => mo.Ignore())
.ForMember(dm => dm.CreatorGuid, mo => mo.Ignore())
.ReverseMap();
CreateMap<MediaItem, Domain.Models.Storage.MediaItem>()
.IncludeBase<SocialEntity, SocialDomainModel>()
.ReverseMap()
.IncludeBase<SocialDomainModel, SocialEntity>();
The test method
[TestMethod]
public void MapMediaTest()
{
var m = MapperConfig.Mapper;
var entity = new MediaItem();
var entityToDomain = m.Map<Domain.Models.Storage.MediaItem>(entity);
Assert.IsTrue(entityToDomain != null);
}
The result
As you can see, the memory floods after ~20 seconds.
It looks like automapper is configured wrong somewhere that causes an infinite loop and results in a StackOverflowException.
My attempt
I've tried every single entity, always same result.
Switched between 5.1.1 and 6.0.2 a bunch of times.
Tried a lot of different configurations.
Switched HashSet collections to normal ICollections.
I'm mapping between entities and domain models, no projection / querying used.
Every entity is retrieved before mapping, no lazy loading.
Note, I do have virtual keywords with other entities and classes and every collection is initialized by default.
I'm out of options, I can't think of what to do besides ditching (not-so)AutoMapper for something else. I'd love some extra insight, thanks!
Edit
The Article class.
/// <summary>
/// Used to store <see cref="Article"/> data.
/// </summary>
[ResourceKey("ARTICLE")]
public class Article : SocialEntity
{
/// <summary>
/// The main <see cref="Title"/> of this <see cref="Article"/>.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// The content <see cref="Body"/> of this <see cref="Article"/>.
/// </summary>
public string Body { get; set; } = string.Empty;
/// <summary>
/// The <see cref="ShortDescription"/> of this <see cref="Article"/>.
/// </summary>
public string ShortDescription { get; set; } = string.Empty;
/// <summary>
/// The long <see cref="Description"/> of this <see cref="Article"/>.
/// </summary>
public string Description { get; set; } = string.Empty;
/// <summary>
/// The <see cref="DateTime"/> that the <see cref="Entity"/> will be available to use.
/// </summary>
public DateTime ExpiresOn { get; set; }
public virtual HashSet<Url> Sources { get; set; } = new HashSet<Url>();
public virtual HashSet<MediaItem> Media { get; set; } = new HashSet<MediaItem>();
public virtual HashSet<Tag> Tags { get; set; } = new HashSet<Tag>();
public virtual HashSet<Hobby> Hobbies { get; set; } = new HashSet<Hobby>();
}
#Richard's answer got me on the right track! Too many navigational properties...
AutoMapper couldn't figure out how to map them all, probably missing configurations.
Entity Framework didn't have any problem with them, just AutoMapper.
I removed most of the navigational properties and it seemed that I didn't need most of them. This solved my issue the quick & easy way!
In my experience with Automapper, you get StackOverflowException when mapping circular references.
I can see that MediaItem has an "Article" property, and Article has a collection of MediaItems.
If your Domain models also have equivalents of these navigation properties, do you need them both ways?
In my case I had a structure like this in my system (both DTO and entity models were the same). There were lists of plans and only in some of them it would throw StackOverflowException. Entity Framework was doing fine having multiple levels of nesting like this, but AutoMapper would fail at ~3 levels. Fortunately the PreviousPlan property was not needed so I just removed it and it worked fine. If you can't afford removing it I would suggest playing with Automapper's MaxDepth.
I know it's not really an answer but maybe will help somebody find the cause.
public class Plan
{
public int Id { get; set; }
public int? PreviousPlanId { get; set; }
public Plan PreviousPlan { get; set; }
...
}

Adding Comments button against list of wcf service

I have call a list of phone records in wcf service. Now i need to add comments & feedback against all records, in such a way that user clicks the notes button and then enter the comments and feedback aaisnt a specific record.
Here is my class FOR notes
public class CallNote
{
public int Id { get; set; }
///<Summary>
///
///</Summary>
public string Comments { get; set; }
///<Summary>
///
///</Summary>
///
public string FeedBack { get; set; }
///<Summary>
///
///</Summary>
///
public string Login { get; set; }
}
Class for callhistory
public class CallHistory
{
/// <summary>
///
/// </summary>
///
public int CallId { get; set; }
/// <summary>
///
/// </summary>
///
public string Login { get; set; }
/// <summary>
///
/// </summary>
public string FirstName { get; set; }
/// <summary>
///
/// </summary>
public string LastName { get; set; }
/// <summary>
///
/// </summary>
public string Email { get; set; }
/// <summary>
///
/// </summary>
public string Country { get; set; }
/// <summary>
///
/// </summary>
public string Phone { get; set; }
/// <summary>
///
/// </summary>
public string DialedNumber { get; set; }
/// <summary>
///
/// </summary>
public string CallStart { get; set; }
/// <summary>
///
/// </summary>
public string CallEnd { get; set; }
/// <summary>
///
/// </summary>
public string TariffDescription { get; set; }
/// <summary>
///
/// </summary>
}
and here is my controller
public ActionResult CreateNote()
{
var result = Manager.GetUsersWhoHaveConsumedFreeCredit();
JavaScriptSerializer serializer = new JavaScriptSerializer();
var callHistory = serializer.Deserialize<List<CallHistory>>(result);
return View(callHistory.ToPagedList(1, Settings.PagingSize));
}
[HttpPost]
public ActionResult CreateNote(CallNote callnote)
{
CallNote_Db db = new CallNote_Db();
if (ModelState.IsValid)
{
db.callnote.Add(callnote);
db.SaveChanges();
return RedirectToAction("CallHistory");
}
return PartialView("CallHistory", callnote);
}
public ActionResult UsersWhoHaveConsumedFreeCredit(int Id = 1)
{
var result = Manager.GetUsersWhoHaveConsumedFreeCredit(); ;
JavaScriptSerializer serializer = new JavaScriptSerializer();
var callHistory = serializer.Deserialize<List<CallHistory>>(result);
CallNote callNote = new Models.CallNote();
CallHistoryFilter callHistoryFilter = new Models.CallHistoryFilter();
MyYelloAdminEntities db = new Models.MyYelloAdminEntities();
{
callHistoryFilter.Countries = db.Countries.ToList();
callHistoryFilter.Countries.Insert(0, new Country { Title = "All", Prefix = "-1" });
var model = new UsersWhoHaveConsumedFreeCredit
{
CallHistory = callHistory.ToPagedList(Id, Settings.PagingSize),
CallHistoryFilter = callHistoryFilter
};
return View(model);
}
}
How i can add comments against each record.
If you want to combine two different WCF data contracts for your UI/display convenience,
then you can create a view model in your MVC project that is composed of these two datacontracts.
And once after you fetch WCF contracts to your MVC app then crate a datamapper(perhaps by using Automapper or a mapper of your choice) between WCF to your ViewModel and reverse the process while pushing data from UI back to WCF service.
Update based on your comments:
Please take a look at AutoMapper, it is a mapping tool to copy your property data from one type of entity to other type based on the mapping strategy you defined.
If you need to maintain comments/feedback at the currently logged in user then, you can track that on server and you need to only get the new comments from UI to controller and prepare the Model to save accordingly.
If you need to maintain comments/feedback at the respective user(the registered user who is providing the comments/feedback against your content) level, then you can maintain a hidden field on view and then you should post the userid and comments back to controller and save them.

How do I compare two instances of a ViewModel for value equality in C#

So I have a ViewModel for searching my database. I intend to refactor away the result set collection. I don't think it belongs in this ViewModel. All I want to do is when the user submits the page is compare the ViewModel passed to my Controller Action with the ViewModel stored in TempData. So I can tell if it is the SameSearch.
So how could that be done? considering I have many many many properties in my viewModel I'd prefer not to compare one by one. And using Json.Encode to serialize them and compare seems "hacky" to me.
Also, if I do have to compare one by one. Should that be done by overriding .Equals()
controller action Search()
public ActionResult Search(SearchViewModel model)
{
SearchViewModel savedSearch = null;
if (Request["submit"].Equals("reset",StringComparison.OrdinalIgnoreCase))
{
TempData.Remove("filter");
model = new SearchViewModel();
}
else if (TempData.ContainsKey("filter"))
{
savedSearch = (SearchViewModel)Convert.ChangeType(TempData["filter"], typeof(SearchViewModel));
}
var currentmodel = System.Web.Helpers.Json.Encode(model);
var savedmodel = System.Web.Helpers.Json.Encode(savedSearch);
if (savedSearch == null)
{
model.NewSearch = true;
}
//The search hasn't changed.
if(currentmodel == savedmodel)
{
model.isSameSearch = true;
}
else
{
model.isSameSearch = false;
}
//do more stuff
//return view
}
ViewModel SearchViewModel
public class SearchViewModel
{
public SearchViewModel()
{
Page = 1;
BuildingSearch = new SearchBuildingViewModel();
ParcelSearch = new SearchParcelViewModel();
SalesSearch = new SearchSalesViewModel();
PropertyFeatureSearch = new SearchPropertyFeaturesViewModel();
ValuesSearch = new SearchValuesViewModel();
}
/// <summary>
/// Gets or sets a value indicating whether use advanced search.
/// </summary>
public bool UseAdvancedSearch { get; set; }
public bool NewSearch { get; set; }
public bool isSameSearch { get; set; }
/// <summary>
/// Gets or sets the page.
/// </summary>
[HiddenInput]
[ScaffoldColumn(false)]
public int Page { get; set; }
[HiddenInput]
[ScaffoldColumn(false)]
public string SortOption { get; set; }
#region Search View Models
/// <summary>
/// Gets or sets the building search.
/// </summary>
public SearchBuildingViewModel BuildingSearch { get; set; }
/// <summary>
/// Gets or sets the parcel search.
/// </summary>
public SearchParcelViewModel ParcelSearch { get; set; }
/// <summary>
/// Gets or sets the property feature search.
/// </summary>
public SearchPropertyFeaturesViewModel PropertyFeatureSearch { get; set; }
/// <summary>
/// Gets or sets the sales search.
/// </summary>
public SearchSalesViewModel SalesSearch { get; set; }
/// <summary>
/// Gets or sets the values search.
/// </summary>
public SearchValuesViewModel ValuesSearch { get; set; }
#endregion
/// <summary>
/// Gets or sets the search results.
/// </summary>
[ScaffoldColumn(false)]
public IPagination<ParcelResultItemViewModel> SearchResults { get; set; }
}
Implement IEquatable<T> in your ViewModel where T is the ViewModel class name, and create your own Equals method logic:
public bool Equals(ViewModel other)
{
//compare properties, etc here
}
And override GetHashCode():
public override int GetHashCode()
{
//you custom hash code algorithm
}
Consider using http://comparenetobjects.codeplex.com/ which will do a deep compare of objects. You can even use NuGet to get it: http://www.nuget.org/packages/CompareNETObjects

Categories

Resources