From f3caabbb8ef29f5c26d5014b358aa915992f3434 Mon Sep 17 00:00:00 2001 From: Omar Piani Date: Tue, 6 Nov 2018 11:35:16 +0100 Subject: [PATCH] Non compiling and maybe failing spike on distinct --- .../LinqRepositoryBase.cs | 23 +++ .../Queries/DistinctOption.cs | 21 ++ .../Queries/DistinctPagingOptions.cs | 110 +++++++++++ .../Queries/DistinctSortingOptions.cs | 65 +++++++ .../Queries/IPostSelectorQueryOptions.cs | 13 ++ .../Queries/PagingOptions.cs | 29 ++- .../Queries/SortingOptions.cs | 45 +++-- SharpRepository.Repository/RepositoryBase.cs | 4 +- .../Controllers/Emails2Controller.cs | 56 ++++++ .../Extensions/EmailExtension.cs | 20 ++ .../SharpRepository.Samples.CoreMvc.csproj | 9 + .../Views/Emails2/Create.cshtml | 35 ++++ .../Views/Emails2/Index.cshtml | 29 +++ .../Views/Shared/_Layout.cshtml | 1 + .../SharpRepository.Samples.csproj | 4 +- .../Data/RepositoryTestCaseDataFactory.cs | 3 - .../RepositoryGetAllTests.cs | 24 +++ .../SharpRepository.Tests.Integration.csproj | 4 +- .../ExecuteForAllRepositoriesAttribute.cs | 4 +- .../ExecuteForAllRepositoriesExcept.cs | 4 +- .../ExecuteForRepositoriesAttribute.cs | 4 +- ...epositoryContactTypeTestCaseDataFactory.cs | 118 +++++++++++ .../QueryOptions/DistinctOptionTests.cs | 30 +++ .../DistinctPagingOptionsTests.cs | 129 ++++++++++++ .../DistinctSortingOptionsTests.cs | 184 ++++++++++++++++++ .../QueryOptions/SortingOptionsTests.cs | 18 ++ .../SharpRepository.Tests.csproj | 4 +- SharpRepository.Tests/TestObjects/Contact.cs | 21 ++ .../TestObjects/ContactType.cs | 19 +- 29 files changed, 998 insertions(+), 32 deletions(-) create mode 100644 SharpRepository.Repository/Queries/DistinctOption.cs create mode 100644 SharpRepository.Repository/Queries/DistinctPagingOptions.cs create mode 100644 SharpRepository.Repository/Queries/DistinctSortingOptions.cs create mode 100644 SharpRepository.Repository/Queries/IPostSelectorQueryOptions.cs create mode 100644 SharpRepository.Samples.CoreMvc/Controllers/Emails2Controller.cs create mode 100644 SharpRepository.Samples.CoreMvc/Extensions/EmailExtension.cs create mode 100644 SharpRepository.Samples.CoreMvc/Views/Emails2/Create.cshtml create mode 100644 SharpRepository.Samples.CoreMvc/Views/Emails2/Index.cshtml create mode 100644 SharpRepository.Tests.Integration/data/RepositoryContactTypeTestCaseDataFactory.cs create mode 100644 SharpRepository.Tests/QueryOptions/DistinctOptionTests.cs create mode 100644 SharpRepository.Tests/QueryOptions/DistinctPagingOptionsTests.cs create mode 100644 SharpRepository.Tests/QueryOptions/DistinctSortingOptionsTests.cs diff --git a/SharpRepository.Repository/LinqRepositoryBase.cs b/SharpRepository.Repository/LinqRepositoryBase.cs index 12b537f4..28a1941d 100644 --- a/SharpRepository.Repository/LinqRepositoryBase.cs +++ b/SharpRepository.Repository/LinqRepositoryBase.cs @@ -70,6 +70,29 @@ protected override IQueryable GetAllQuery(IQueryOptions queryOptions, IFet return query; } + protected override IQueryable GetAllQuery(IQueryOptions queryOptions, IFetchStrategy fetchStrategy, Expression> selector) + { + if (queryOptions is DistinctOption || queryOptions is DistinctSortingOptions || queryOptions is DistinctPagingOptions || queryOptions is DistinctSortingOptions || queryOptions is DistinctPagingOptions) + { + if (queryOptions == null) + return GetAllQuery(fetchStrategy).Select(selector); + + var query = BaseQuery(fetchStrategy).Select(selector); + + query = queryOptions.Apply(query); + + SetTraceInfo("GetAll", query); + + return query; + } else + { + return GetAllQuery(queryOptions, fetchStrategy).Select(selector); + } + + + } + + protected override IQueryable FindAllQuery(ISpecification criteria) { var query = BaseQuery(criteria.FetchStrategy); diff --git a/SharpRepository.Repository/Queries/DistinctOption.cs b/SharpRepository.Repository/Queries/DistinctOption.cs new file mode 100644 index 00000000..dc36988d --- /dev/null +++ b/SharpRepository.Repository/Queries/DistinctOption.cs @@ -0,0 +1,21 @@ +using System.Linq; + +namespace SharpRepository.Repository.Queries +{ + /// + /// Used to define the paging and/or sorting criteria on queries run against a repository. + /// + /// The entity type of the repository. + public class DistinctOption : IQueryOptions, IPostSelectorQueryOptions + { + public IQueryable Apply(IQueryable query) + { + return query.Distinct(); + } + + public IQueryable Apply(IQueryable query) + { + return query.Distinct(); + } + } +} diff --git a/SharpRepository.Repository/Queries/DistinctPagingOptions.cs b/SharpRepository.Repository/Queries/DistinctPagingOptions.cs new file mode 100644 index 00000000..68f225fb --- /dev/null +++ b/SharpRepository.Repository/Queries/DistinctPagingOptions.cs @@ -0,0 +1,110 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace SharpRepository.Repository.Queries +{ + /// + /// Used to define the paging criteria on queries run against a repository. + /// + /// The entity type of the repository. + /// The type of the property that is being sorted. + public class DistinctPagingOptions : PagingOptions + { + public DistinctPagingOptions(int pageNumber, int pageSize, Expression> sortExpression, bool isDescending = false) + : base(pageNumber, pageSize, sortExpression, isDescending) { } + + /// + /// Applies paging to the specified query. + /// + /// The query. + /// Paged results. + public override IQueryable Apply(IQueryable query) + { + var distinctQuery = query.Distinct(); + return base.Apply(distinctQuery); + } + + /// + /// Used in compiling a unique key for a query + /// + /// Unique key for a query + public override string ToString() + { + return "Distinct" + base.ToString(); + } + } + + /// + /// Used to define the paging criteria on queries run against a repository. + /// + /// The entity type of the repository. + public class DistinctPagingOptions : DistinctSortingOptions, IPagingOptions + { + public int PageSize { get; set; } + public int PageNumber { get; set; } + public int Skip { get { return (PageNumber - 1) * PageSize; } } + public int Take { get { return PageSize; } } + public int TotalItems { get; set; } + + public DistinctPagingOptions(int pageNumber, int pageSize, string sortProperty, bool isDescending = false) + : base(sortProperty, isDescending) + { + PageSize = pageSize; + PageNumber = pageNumber; + } + + + /// + /// Applies paging to the specified query. + /// + /// The query. + /// Paged results. + public override IQueryable Apply(IQueryable query) + { + query = base.Apply(query); + + TotalItems = query.Count(); + + if (Skip > 0 || Take > 0) + { + return query.Skip(Skip).Take(Take); + } + + return query; + } + + /// + /// Applies paging to the specified query. + /// + /// The query. + /// Paged results. + public override IQueryable Apply(IQueryable query) + { + query = base.Apply(query); + + TotalItems = query.Count(); + + if (Skip > 0 || Take > 0) + { + return query.Skip(Skip).Take(Take); + } + + return query; + } + + /// + /// Used in compiling a unique key for a query + /// + /// Unique key for a query + public override string ToString() + { + return string.Format("DistinctPagingOptions<{0}>\nPageSize: {1}\nPageNumber: {2}\nSort: {3}", + typeof(T).Name, + PageSize, + PageNumber, + base.ToString() + ); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Repository/Queries/DistinctSortingOptions.cs b/SharpRepository.Repository/Queries/DistinctSortingOptions.cs new file mode 100644 index 00000000..621d52e3 --- /dev/null +++ b/SharpRepository.Repository/Queries/DistinctSortingOptions.cs @@ -0,0 +1,65 @@ +using System; +using System.Linq; +using System.Linq.Expressions; + +namespace SharpRepository.Repository.Queries +{ + /// + /// Used to define the sorting on queries with distinct run against a repository. + /// + /// The entity type of the repository. + /// The type of the property that is being sorted. + public class DistinctSortingOptions : SortingOptions + { + public DistinctSortingOptions(Expression> sortExpression, bool isDescending = false) : base(sortExpression, isDescending) + { + } + + /// + /// Applies sorting to the specified query. + /// + /// The query. + /// Sorted results. + public override IQueryable Apply(IQueryable query) + { + return base.Apply(query).Distinct(); + } + + /// + /// Used in compiling a unique key for a query + /// + /// Unique key for a query + public override string ToString() + { + return "Distinct" + base.ToString(); + } + } + + /// + /// Used to define the sorting on queries with distinct run against a repository. + /// + /// The entity type of the repository. + public class DistinctSortingOptions : SortingOptions + { + public DistinctSortingOptions(string sortProperty, bool isDescending = false) : base(sortProperty, isDescending) { } + + /// + /// Applies sorting to the specified query. + /// + /// The query. + /// Sorted results. + public override IQueryable Apply(IQueryable query) + { + return base.Apply(query).Distinct(); + } + + /// + /// Used in compiling a unique key for a query + /// + /// Unique key for a query + public override string ToString() + { + return "Distinct" + base.ToString(); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Repository/Queries/IPostSelectorQueryOptions.cs b/SharpRepository.Repository/Queries/IPostSelectorQueryOptions.cs new file mode 100644 index 00000000..8ccafb1a --- /dev/null +++ b/SharpRepository.Repository/Queries/IPostSelectorQueryOptions.cs @@ -0,0 +1,13 @@ +using System.Linq; + +namespace SharpRepository.Repository.Queries +{ + /// + /// Used to define the paging and/or sorting criteria on queries run against a repository. + /// + /// The entity type of the repository. + public interface IPostSelectorQueryOptions + { + IQueryable Apply(IQueryable query); + } +} diff --git a/SharpRepository.Repository/Queries/PagingOptions.cs b/SharpRepository.Repository/Queries/PagingOptions.cs index 11dfed30..824e5516 100644 --- a/SharpRepository.Repository/Queries/PagingOptions.cs +++ b/SharpRepository.Repository/Queries/PagingOptions.cs @@ -49,9 +49,9 @@ public override IQueryable Apply(IQueryable query) /// Unique key for a query public override string ToString() { - return String.Format("PagingOptions<{0},{1}>\nPageSize: {2}\nPageNumber: {3}\nSort: {4}", - (typeof(T)).Name, - (typeof(TSortKey)).Name, + return string.Format("PagingOptions<{0},{1}>\nPageSize: {2}\nPageNumber: {3}\nSort: {4}", + typeof(T).Name, + typeof(TSortKey).Name, PageSize, PageNumber, base.ToString() @@ -97,14 +97,33 @@ public override IQueryable Apply(IQueryable query) return query; } + /// + /// Applies paging to the specified query. + /// + /// The query. + /// Paged results. + public override IQueryable Apply(IQueryable query) + { + query = base.Apply(query); + + TotalItems = query.Count(); + + if (Skip > 0 || Take > 0) + { + return query.Skip(Skip).Take(Take); + } + + return query; + } + /// /// Used in compiling a unique key for a query /// /// Unique key for a query public override string ToString() { - return String.Format("PagingOptions<{0}>\nPageSize: {1}\nPageNumber: {2}\nSort: {3}", - (typeof(T)).Name, + return string.Format("PagingOptions<{0}>\nPageSize: {1}\nPageNumber: {2}\nSort: {3}", + typeof(T).Name, PageSize, PageNumber, base.ToString() diff --git a/SharpRepository.Repository/Queries/SortingOptions.cs b/SharpRepository.Repository/Queries/SortingOptions.cs index 4aa092e6..5c99c86e 100644 --- a/SharpRepository.Repository/Queries/SortingOptions.cs +++ b/SharpRepository.Repository/Queries/SortingOptions.cs @@ -31,7 +31,7 @@ public SortingOptions(Expression> sortExpression, bool isDesce _primarySortAction = q => q.OrderBy(sortExpression); } - _primarySortToString = String.Format("{0}-{1}", sortExpression, isDescending); + _primarySortToString = string.Format("{0}-{1}", sortExpression, isDescending); } public void ThenSortBy(Expression> sortExpression, bool isDescending = false) @@ -48,7 +48,7 @@ public void ThenSortBy(Expression> sortExpress } _sortActions.Add(sortAction); - _sortActionsToString.Add(String.Format("{0}-{1}", sortExpression, isDescending)); + _sortActionsToString.Add(string.Format("{0}-{1}", sortExpression, isDescending)); } @@ -75,11 +75,11 @@ public virtual IQueryable Apply(IQueryable query) /// Unique key for a query public override string ToString() { - return String.Format("SortingOptions<{0},{1}>\nSort: {2}\nExtra: {3}", - (typeof(T)).Name, - (typeof(TSortKey)).Name, - _primarySortToString ?? "null", - String.Join("-", _sortActionsToString) + return string.Format("SortingOptions<{0},{1}>\nSort: {2}\nExtra: {3}", + typeof(T).Name, + typeof(TSortKey).Name, + _primarySortToString ?? "null", + string.Join("-", _sortActionsToString) ); } } @@ -108,7 +108,7 @@ public SortingOptions(string sortProperty, bool isDescending = false) _primarySortAction = q => q.OrderByProperty(sortProperty); } - _primarySortToString = String.Format("{0}-{1}", sortProperty, isDescending); + _primarySortToString = string.Format("{0}-{1}", sortProperty, isDescending); } public void ThenSortBy(string sortProperty, bool isDescending = false) @@ -125,7 +125,7 @@ public void ThenSortBy(string sortProperty, bool isDescending = false) } _sortActions.Add(sortAction); - _sortActionsToString.Add(String.Format("{0}-{1}", sortProperty, isDescending)); + _sortActionsToString.Add(string.Format("{0}-{1}", sortProperty, isDescending)); } /// @@ -147,16 +147,35 @@ public virtual IQueryable Apply(IQueryable query) return _sortActions.Aggregate(sortedQuery, (current, sortAction) => sortAction(current)); } + /// + /// Applies sorting to the specified query. + /// + /// The query. + /// Sorted results. + public virtual IQueryable Apply(IQueryable query) + { + // TODO: do we need to deal with the case where the user passes in "Name desc", should we strip the desc out, or let it override the isDescending param, or not deal with it and blame it on the user? + + IOrderedQueryable sortedQuery = null; + + if (_primarySortAction != null) + { + sortedQuery = _primarySortAction(query); + } + + return _sortActions.Aggregate(sortedQuery, (current, sortAction) => sortAction(current)); + } + /// /// Used in compiling a unique key for a query /// /// Unique key for a query public override string ToString() { - var val = String.Format("SortingOptions<{0}>\nSort: {1}\nExtra: {2}", - (typeof(T)).Name, - _primarySortToString ?? "null", - String.Join("-", _sortActionsToString) + var val = string.Format("SortingOptions<{0}>\nSort: {1}\nExtra: {2}", + typeof(T).Name, + _primarySortToString ?? "null", + string.Join("-", _sortActionsToString) ); return val; } diff --git a/SharpRepository.Repository/RepositoryBase.cs b/SharpRepository.Repository/RepositoryBase.cs index 7c4fdc84..4777a751 100644 --- a/SharpRepository.Repository/RepositoryBase.cs +++ b/SharpRepository.Repository/RepositoryBase.cs @@ -126,6 +126,8 @@ public bool CachingEnabled protected abstract IQueryable GetAllQuery(IFetchStrategy fetchStrategy); protected abstract IQueryable GetAllQuery(IQueryOptions queryOptions, IFetchStrategy fetchStrategy); + protected abstract IQueryable GetAllQuery(IQueryOptions queryOptions, IFetchStrategy fetchStrategy, Expression> selector); + //Managing aspects protected void DisableAspect(Type aspectType) { @@ -271,7 +273,7 @@ public IEnumerable GetAll(Expression> selecto if (context.Specification == null) { results = QueryManager.ExecuteGetAll( - () => GetAllQuery(context.QueryOptions, fetchStrategy).Select(context.Selector).ToList(), + () => GetAllQuery(context.QueryOptions, fetchStrategy, context.Selector).ToList(), context.Selector, context.QueryOptions ); diff --git a/SharpRepository.Samples.CoreMvc/Controllers/Emails2Controller.cs b/SharpRepository.Samples.CoreMvc/Controllers/Emails2Controller.cs new file mode 100644 index 00000000..ca43ab07 --- /dev/null +++ b/SharpRepository.Samples.CoreMvc/Controllers/Emails2Controller.cs @@ -0,0 +1,56 @@ +using Microsoft.AspNetCore.Mvc; +using SharpRepository.CoreMvc.Models; +using SharpRepository.Repository; +using SharpRepository.Repository.Queries; +using SharpRepository.Samples.CoreMvc.CustomRepositories; +using SharpRepository.Samples.CoreMvc.Extensions; +using System.Collections.Generic; +using System.Linq; + +namespace SharpRepository.CoreMvc.Controllers +{ + public class Emails2Controller : Controller + { + protected IRepository repository; + protected IRepository repositoryContacts; + + public Emails2Controller(IRepository repository, IRepository contactRepository) + { + this.repository = repository; + this.repositoryContacts = contactRepository; + } + + // GET: Mails + public ActionResult Index() + { + var mails = repository.GetMails().ToArray(); + //var mails = repositoryContacts.GetAll(r => r.Emails, new PagingOptions(1, 2, x => x.Name)); + + return View(mails); + } + + public ActionResult Create(string id) + { + ViewBag.ContactId = id; + var email = new Email(); + return View(email); + } + + [HttpPost] + public ActionResult Create(Email email) + { + string contactId = this.Request.Form["ContactId"]; + var contact = repositoryContacts.Get(contactId); + + if (contact.Emails == null) + { + contact.Emails = new List(); + } + + contact.Emails.Add(email); + repositoryContacts.Update(contact); + + return RedirectToAction("Index"); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Samples.CoreMvc/Extensions/EmailExtension.cs b/SharpRepository.Samples.CoreMvc/Extensions/EmailExtension.cs new file mode 100644 index 00000000..78df2d98 --- /dev/null +++ b/SharpRepository.Samples.CoreMvc/Extensions/EmailExtension.cs @@ -0,0 +1,20 @@ +using SharpRepository.CoreMvc.Models; +using SharpRepository.Repository; +using SharpRepository.Repository.Configuration; +using SharpRepository.Repository.Queries; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace SharpRepository.Samples.CoreMvc.Extensions +{ + public static class EmailRepositoryExtension + { + + public static IEnumerable GetMails(this IRepository repo) + { + return repo.GetAll(m => m.EmailAddress, new DistinctPagingOptions(1, 2, "EmailAddress")); + } + } +} diff --git a/SharpRepository.Samples.CoreMvc/SharpRepository.Samples.CoreMvc.csproj b/SharpRepository.Samples.CoreMvc/SharpRepository.Samples.CoreMvc.csproj index 4218d3c0..6e1085dd 100644 --- a/SharpRepository.Samples.CoreMvc/SharpRepository.Samples.CoreMvc.csproj +++ b/SharpRepository.Samples.CoreMvc/SharpRepository.Samples.CoreMvc.csproj @@ -21,4 +21,13 @@ + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + diff --git a/SharpRepository.Samples.CoreMvc/Views/Emails2/Create.cshtml b/SharpRepository.Samples.CoreMvc/Views/Emails2/Create.cshtml new file mode 100644 index 00000000..49e7c7f5 --- /dev/null +++ b/SharpRepository.Samples.CoreMvc/Views/Emails2/Create.cshtml @@ -0,0 +1,35 @@ +@model SharpRepository.CoreMvc.Models.Email + +@{ + ViewData["Title"] = "Create"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

Create

+ +

Contact

+
+
+
+
+ +
+
+ + + +
+
+ +
+
+
+
+ + + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/SharpRepository.Samples.CoreMvc/Views/Emails2/Index.cshtml b/SharpRepository.Samples.CoreMvc/Views/Emails2/Index.cshtml new file mode 100644 index 00000000..8e88e421 --- /dev/null +++ b/SharpRepository.Samples.CoreMvc/Views/Emails2/Index.cshtml @@ -0,0 +1,29 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "View"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

View

+ + + + + + + + + + @foreach (var item in Model) + { + + + + } + +
+ Email +
+ @item +
diff --git a/SharpRepository.Samples.CoreMvc/Views/Shared/_Layout.cshtml b/SharpRepository.Samples.CoreMvc/Views/Shared/_Layout.cshtml index 34719af9..993f06d2 100644 --- a/SharpRepository.Samples.CoreMvc/Views/Shared/_Layout.cshtml +++ b/SharpRepository.Samples.CoreMvc/Views/Shared/_Layout.cshtml @@ -34,6 +34,7 @@
  • About
  • Contacts
  • Emails
  • +
  • Emails2
  • diff --git a/SharpRepository.Samples/SharpRepository.Samples.csproj b/SharpRepository.Samples/SharpRepository.Samples.csproj index ed4fa1f4..39a0d8a7 100644 --- a/SharpRepository.Samples/SharpRepository.Samples.csproj +++ b/SharpRepository.Samples/SharpRepository.Samples.csproj @@ -8,8 +8,8 @@ - - + + diff --git a/SharpRepository.Tests.Integration/Data/RepositoryTestCaseDataFactory.cs b/SharpRepository.Tests.Integration/Data/RepositoryTestCaseDataFactory.cs index 14910bfb..9f051e55 100644 --- a/SharpRepository.Tests.Integration/Data/RepositoryTestCaseDataFactory.cs +++ b/SharpRepository.Tests.Integration/Data/RepositoryTestCaseDataFactory.cs @@ -1,6 +1,4 @@ using System.Collections.Generic; -//using System.Data.Entity; -//using System.Data.Entity.Infrastructure; using System.Linq; using NUnit.Framework; using Raven.Client.Document; @@ -111,7 +109,6 @@ public static IEnumerable Build(RepositoryType[] includeType, stri yield return new TestCaseData(new CouchDbRepository(CouchDbUrl.Host, CouchDbUrl.Port, databaseName)).SetName("CouchDbRepository " + testName); } - } } } diff --git a/SharpRepository.Tests.Integration/RepositoryGetAllTests.cs b/SharpRepository.Tests.Integration/RepositoryGetAllTests.cs index 46fbd25e..fc3f8e54 100644 --- a/SharpRepository.Tests.Integration/RepositoryGetAllTests.cs +++ b/SharpRepository.Tests.Integration/RepositoryGetAllTests.cs @@ -47,6 +47,30 @@ public void GetAll_Should_Return_Every_Items_With_Paging(IRepository repository) + { + const int resultingPage = 2; + const int pageSize = 2; + const int totalItems = 5; + + var queryOptions = new DistinctPagingOptions(resultingPage, pageSize, "Name"); + + for (int i = 1; i <= totalItems; i++) + { + var contactType = new ContactType { Name = "Test Type " + i }; + repository.Add(contactType); + + var contactType2 = new ContactType { Name = "Test Type " + i }; + repository.Add(contactType2); + } + + IEnumerable result = repository.GetAll(queryOptions).ToList(); + result.Count().ShouldBe(pageSize); + queryOptions.TotalItems.ShouldBe(totalItems); + result.First().Name.ShouldBe("Test Type 3"); + } + [ExecuteForAllRepositories] public void GetAll_With_Selector_Should_Return_Every_Item(IRepository repository) { diff --git a/SharpRepository.Tests.Integration/SharpRepository.Tests.Integration.csproj b/SharpRepository.Tests.Integration/SharpRepository.Tests.Integration.csproj index e4a4201c..a4ce8fb7 100644 --- a/SharpRepository.Tests.Integration/SharpRepository.Tests.Integration.csproj +++ b/SharpRepository.Tests.Integration/SharpRepository.Tests.Integration.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesAttribute.cs b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesAttribute.cs index 0bcca57e..44d99163 100644 --- a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesAttribute.cs +++ b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesAttribute.cs @@ -10,7 +10,9 @@ public class ExecuteForAllRepositoriesAttribute : TestCaseSourceAttribute private static IEnumerable ForAllRepositoriesTestCaseData { - get { return RepositoryTestCaseDataFactory.Build(RepositoryTypes.All, _testName); } + get { return _testName == "ContactTypeTest" + ? RepositoryContactTypeTestCaseDataFactory.Build(RepositoryTypes.All, _testName) + : RepositoryTestCaseDataFactory.Build(RepositoryTypes.All, _testName); } } public ExecuteForAllRepositoriesAttribute(string testName = "Test") : base(typeof(ExecuteForAllRepositoriesAttribute), "ForAllRepositoriesTestCaseData") diff --git a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesExcept.cs b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesExcept.cs index aa092ea1..eec1e97f 100644 --- a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesExcept.cs +++ b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForAllRepositoriesExcept.cs @@ -11,7 +11,9 @@ private static IEnumerable ForAllRepositoriesExceptTestCaseData { get { - return RepositoryTestCaseDataFactory.Build(RemoveExceptions(RepositoryTypes.All), _testName); + return _testName == "ContactTypeTest" + ? RepositoryContactTypeTestCaseDataFactory.Build(RepositoryTypes.All, _testName) + : RepositoryTestCaseDataFactory.Build(RepositoryTypes.All, _testName); } } diff --git a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForRepositoriesAttribute.cs b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForRepositoriesAttribute.cs index 50e620b0..2216e650 100644 --- a/SharpRepository.Tests.Integration/TestAttributes/ExecuteForRepositoriesAttribute.cs +++ b/SharpRepository.Tests.Integration/TestAttributes/ExecuteForRepositoriesAttribute.cs @@ -12,7 +12,9 @@ private static IEnumerable ForRepositoriesTestCaseData { get { - return RepositoryTestCaseDataFactory.Build(_includeType, _testName); + return _testName == "ContactTypeTest" + ? RepositoryContactTypeTestCaseDataFactory.Build(_includeType, _testName) + : RepositoryTestCaseDataFactory.Build(_includeType, _testName); } } diff --git a/SharpRepository.Tests.Integration/data/RepositoryContactTypeTestCaseDataFactory.cs b/SharpRepository.Tests.Integration/data/RepositoryContactTypeTestCaseDataFactory.cs new file mode 100644 index 00000000..b052638d --- /dev/null +++ b/SharpRepository.Tests.Integration/data/RepositoryContactTypeTestCaseDataFactory.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +//using System.Data.Entity; +//using System.Data.Entity.Infrastructure; +using System.Linq; +using NUnit.Framework; +using Raven.Client.Document; +using Raven.Client.Embedded; +using SharpRepository.CouchDbRepository; +using SharpRepository.Db4oRepository; +using SharpRepository.Tests.Integration.TestObjects; +using SharpRepository.XmlRepository; +using SharpRepository.EfRepository; +using SharpRepository.EfCoreRepository; +using SharpRepository.RavenDbRepository; +using SharpRepository.MongoDbRepository; +using SharpRepository.InMemoryRepository; +using SharpRepository.CacheRepository; +using System; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using SharpRepository.Repository.Caching; +using Microsoft.Extensions.Caching.Memory; +using Raven.Client; + +namespace SharpRepository.Tests.Integration.Data +{ + public class RepositoryContactTypeTestCaseDataFactory + { + public static IEnumerable Build(RepositoryType[] includeType, string testName = "Test") + { + if (includeType.Contains(RepositoryType.InMemory)) + { + yield return new TestCaseData(new InMemoryRepository()).SetName("InMemoryRepository " + testName); + } + + if (includeType.Contains(RepositoryType.Xml)) + { + var xmlDataDirectoryPath = XmlDataDirectoryFactory.Build("ContactType"); + yield return + new TestCaseData(new XmlRepository(xmlDataDirectoryPath)).SetName("XmlRepository" + testName); + } + + if (includeType.Contains(RepositoryType.Ef)) + { + var dbPath = EfDataDirectoryFactory.Build(); + yield return + new TestCaseData(new EfRepository(new TestObjectContext("Data Source=" + dbPath))).SetName("EfRepository" + testName); + } + + if (includeType.Contains(RepositoryType.EfCore)) + { + var connection = new SqliteConnection("DataSource=:memory:"); + connection.Open(); + + var options = new DbContextOptionsBuilder() + .UseSqlite(connection) + .Options; + + // Create the schema in the database + var context = new TestObjectContextCore(options); + context.Database.EnsureCreated(); + yield return new TestCaseData(new EfCoreRepository(context)).SetName("EfCoreRepository " + testName); + } + + if (includeType.Contains(RepositoryType.Dbo4)) + { + var dbPath = Db4oDataDirectoryFactory.Build("ContactType"); + yield return new TestCaseData(new Db4oRepository(dbPath)).SetName("Db4oRepository " + testName); + } + + if (includeType.Contains(RepositoryType.MongoDb)) + { + string connectionString = MongoDbConnectionStringFactory.Build("ContactType"); + + if (MongoDbRepositoryManager.ServerIsRunning(connectionString)) + { + MongoDbRepositoryManager.DropDatabase(connectionString); // Pre-test cleanup + yield return new TestCaseData(new MongoDbRepository(connectionString)).SetName("MongoDb " + testName); + } + } + + if (includeType.Contains(RepositoryType.RavenDb)) + { + var documentStore = new EmbeddableDocumentStore + { + RunInMemory = true, + DataDirectory = "~\\Data\\RavenDb" + }; + if (IntPtr.Size == 4) + { + documentStore.Configuration.Storage.Voron.AllowOn32Bits = true; + } + + IDocumentStore x = new EmbeddableDocumentStore(); + yield return new TestCaseData(new RavenDbRepository(documentStore: documentStore)).SetName("RavenDbRepository " + testName); + } + + if (includeType.Contains(RepositoryType.Cache)) + { + var cachingProvider = new InMemoryCachingProvider(new MemoryCache(new MemoryCacheOptions())); + yield return new TestCaseData(new CacheRepository(CachePrefixFactory.Build(), cachingProvider)).SetName("CacheRepository " + testName); + } + + if (includeType.Contains(RepositoryType.CouchDb)) + { + if (CouchDbRepositoryManager.ServerIsRunning(CouchDbUrl.Host, CouchDbUrl.Port)) + { + var databaseName = CouchDbDatabaseNameFactory.Build("ContactType"); + CouchDbRepositoryManager.DropDatabase(CouchDbUrl.Host, CouchDbUrl.Port, databaseName); + CouchDbRepositoryManager.CreateDatabase(CouchDbUrl.Host, CouchDbUrl.Port, databaseName); + + yield return new TestCaseData(new CouchDbRepository(CouchDbUrl.Host, CouchDbUrl.Port, databaseName)).SetName("CouchDbRepository " + testName); + } + + } + } + } +} \ No newline at end of file diff --git a/SharpRepository.Tests/QueryOptions/DistinctOptionTests.cs b/SharpRepository.Tests/QueryOptions/DistinctOptionTests.cs new file mode 100644 index 00000000..a39f2798 --- /dev/null +++ b/SharpRepository.Tests/QueryOptions/DistinctOptionTests.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using SharpRepository.Repository.Queries; +using SharpRepository.Tests.TestObjects; +using Shouldly; + +namespace SharpRepository.Tests.QueryOptions +{ + [TestFixture] + public class DistinctOptionTests : TestBase + { + [Test] + public void DistinctOption_Will_Hide_Duplicates() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new DistinctOption(); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 5"); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Tests/QueryOptions/DistinctPagingOptionsTests.cs b/SharpRepository.Tests/QueryOptions/DistinctPagingOptionsTests.cs new file mode 100644 index 00000000..4b29d327 --- /dev/null +++ b/SharpRepository.Tests/QueryOptions/DistinctPagingOptionsTests.cs @@ -0,0 +1,129 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using SharpRepository.Repository.Queries; +using SharpRepository.Tests.TestObjects; +using Shouldly; + +namespace SharpRepository.Tests.QueryOptions +{ + [TestFixture] + public class DistinctPagingOptionsTests : TestBase + { + [Test] + public void DistinctPagingOptions_PageNumber_Will_Be_Set_In_Constructor() + { + new DistinctPagingOptions(1, 10, "Name").PageNumber.ShouldBe(1); + new DistinctPagingOptions(1, 10, m => m.Name).PageNumber.ShouldBe(1); + } + + [Test] + public void DistinctPagingOptions_PageSize_Will_Be_Set_In_Constructor() + { + new DistinctPagingOptions(1, 10, "Name").PageSize.ShouldBe(10); + new DistinctPagingOptions(1, 10, m => m.Name).PageSize.ShouldBe(10); + } + + [Test] + public void DistinctPagingOptions_Apply_Will_Set_TotalItems() + { + var contacts = new List(); + for (int i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + const int resultingPage = 2; + const int pageSize = 2; + var qo = new DistinctPagingOptions(resultingPage, pageSize, "Name", isDescending: true); + qo.Apply(contacts.AsQueryable()); + qo.TotalItems.ShouldBe(5); + + var qo2 = new DistinctPagingOptions(resultingPage, pageSize, x => x.Name, isDescending: true); + qo2.Apply(contacts.AsQueryable()); + qo2.TotalItems.ShouldBe(5); + } + + [Test] + public void DistinctPagingOptions_Apply_Return_Requested_Page() + { + var contacts = new List(); + for (int i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + const int resultingPage = 2; + const int pageSize = 2; + + var qo = new DistinctPagingOptions(resultingPage, pageSize, "Name", isDescending: true); + IQueryable queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(2); + queryable.First().Name.ShouldBe("Test User 3"); + + var qo2 = new DistinctPagingOptions(resultingPage, pageSize, x => x.Name, isDescending: true); + queryable = qo2.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(2); + queryable.First().Name.ShouldBe("Test User 3"); + } + + [Test] + public void DistinctPagingOptions_Apply_Will_Set_TotalItems_With_Multiple_Sort() + { + var contacts = new List(); + for (int i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + const int resultingPage = 2; + const int pageSize = 2; + var qo = new DistinctPagingOptions(resultingPage, pageSize, "Name", isDescending: true); + qo.ThenSortBy("ContactTypeId"); + qo.Apply(contacts.AsQueryable()); + qo.TotalItems.ShouldBe(5); + + var qo2 = new DistinctPagingOptions(resultingPage, pageSize, x => x.Name, isDescending: true); + qo2.ThenSortBy(x => x.ContactTypeId); + qo2.Apply(contacts.AsQueryable()); + qo2.TotalItems.ShouldBe(5); + } + + [Test] + public void DistinctPagingOptions_Apply_Return_Requested_Page_With_Multiple_Sort() + { + var contacts = new List(); + for (int i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + (i % 2), ContactTypeId = i}); + contacts.Add(new Contact { Name = "Test User " + (i % 2), ContactTypeId = i }); + } + + const int resultingPage = 2; + const int pageSize = 2; + + var qo = new DistinctPagingOptions(resultingPage, pageSize, "Name", isDescending: true); + qo.ThenSortBy("ContactTypeId", isDescending: true); + + IQueryable queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(2); + + var contact = queryable.First(); + contact.Name.ShouldBe("Test User 1"); + contact.ContactTypeId.ShouldBe(1); + + var qo2 = new DistinctPagingOptions(resultingPage, pageSize, x => x.Name, isDescending: true); + qo2.ThenSortBy(x => x.ContactTypeId, isDescending: true); + + queryable = qo2.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(2); + + contact = queryable.First(); + contact.Name.ShouldBe("Test User 1"); + contact.ContactTypeId.ShouldBe(1); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Tests/QueryOptions/DistinctSortingOptionsTests.cs b/SharpRepository.Tests/QueryOptions/DistinctSortingOptionsTests.cs new file mode 100644 index 00000000..7f73e7e4 --- /dev/null +++ b/SharpRepository.Tests/QueryOptions/DistinctSortingOptionsTests.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using SharpRepository.Repository.Queries; +using SharpRepository.Tests.TestObjects; +using Shouldly; + +namespace SharpRepository.Tests.QueryOptions +{ + [TestFixture] + public class DistinctSortingOptionsTests : TestBase + { + [Test] + public void DistinctSortingOptions_Will_Sort_By_SortProperty_Asc() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new DistinctSortingOptions("Name"); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 1"); + } + + [Test] + public void DistinctSortingOptions_Will_Sort_By_SortProperty_Desc() + { + var contacts = new List(); + for (var i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new DistinctSortingOptions("Name", true); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 5"); + } + + [Test] + public void DistinctSortingOptions_With_Multiple_Sorting_Properties() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + (i % 2),ContactTypeId = i}); + contacts.Add(new Contact { Name = "Test User " + (i % 2), ContactTypeId = i }); + } + + var qo = new DistinctSortingOptions("Name"); + qo.ThenSortBy("ContactTypeId"); + + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + + var contact = queryable.First(); + contact.Name.ShouldBe("Test User 0"); + contact.ContactTypeId.ShouldBe(2); + } + + [Test] + public void DistinctSortingOptions_Will_Sort_By_SortExpression_Asc() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new DistinctSortingOptions(x => x.Name); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 1"); + } + + [Test] + public void DistinctSortingOptions_Will_Sort_By_SortExpression_Desc() + { + var contacts = new List(); + for (var i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new DistinctSortingOptions(x => x.Name, isDescending: true); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 5"); + } + + [Test] + public void SortingOptions_With_Multiple_SortExpression_Properties() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + (i % 2), ContactTypeId = i }); + contacts.Add(new Contact { Name = "Test User " + (i % 2),ContactTypeId = i}); + } + + var qo = new DistinctSortingOptions(x => x.Name); + qo.ThenSortBy(x => x.ContactTypeId); + + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + + var contact = queryable.First(); + contact.Name.ShouldBe("Test User 0"); + contact.ContactTypeId.ShouldBe(2); + } + + [Test] + public void SortingOptions_Will_Sort_By_SortExpression_Multiple_Deep_Asc() + { + var contacts = new List(); + for (var i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + } + + var qo = new DistinctSortingOptions(x => x.ContactType.Name); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 5"); + } + + [Test] + public void SortingOptions_Will_Sort_By_SortExpression_Multiple_Deep_Desc() + { + var contacts = new List(); + for (var i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + } + + var qo = new DistinctSortingOptions(x => x.ContactType.Name, isDescending: true); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 1"); + } + + [Test] + public void DistinctSortingOptions_Will_Sort_By_Multiple_Deep_SortProperty_Asc() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + } + + var qo = new DistinctSortingOptions("ContactType.Name"); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 5"); + } + + [Test] + public void DistinctSortingOptions_Will_Sort_By_Multiple_Deep_SortProperty_Desc() + { + var contacts = new List(); + for (var i = 1; i <= 5; i++) + { + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + contacts.Add(new Contact { Name = "Test User " + i, ContactType = new ContactType { Name = "Type " + (5 - i) } }); + } + + var qo = new DistinctSortingOptions("ContactType.Name", isDescending: true); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(5); + queryable.First().Name.ShouldBe("Test User 1"); + } + } +} \ No newline at end of file diff --git a/SharpRepository.Tests/QueryOptions/SortingOptionsTests.cs b/SharpRepository.Tests/QueryOptions/SortingOptionsTests.cs index 9e62bbc7..c36fee7e 100644 --- a/SharpRepository.Tests/QueryOptions/SortingOptionsTests.cs +++ b/SharpRepository.Tests/QueryOptions/SortingOptionsTests.cs @@ -26,6 +26,24 @@ public void SortingOptions_Will_Sort_By_SortProperty_Asc() queryable.First().Name.ShouldBe("Test User 1"); } + [Test] + public void SortingOptions_Will_Sort_By_SortProperty_Asc_Not_Distinct() + { + var contacts = new List(); + for (var i = 5; i >= 1; i--) + { + contacts.Add(new Contact { Name = "Test User " + i }); + contacts.Add(new Contact { Name = "Test User " + i }); + } + + var qo = new SortingOptions("Name"); + var queryable = qo.Apply(contacts.AsQueryable()); + queryable.Count().ShouldBe(10); + queryable.First().Name.ShouldBe("Test User 1"); + } + + + [Test] public void SortingOptions_Will_Sort_By_SortProperty_Desc() { diff --git a/SharpRepository.Tests/SharpRepository.Tests.csproj b/SharpRepository.Tests/SharpRepository.Tests.csproj index 3762e1ff..b816c6e7 100644 --- a/SharpRepository.Tests/SharpRepository.Tests.csproj +++ b/SharpRepository.Tests/SharpRepository.Tests.csproj @@ -13,8 +13,8 @@ - - + + diff --git a/SharpRepository.Tests/TestObjects/Contact.cs b/SharpRepository.Tests/TestObjects/Contact.cs index 5dca3862..ed7e9664 100644 --- a/SharpRepository.Tests/TestObjects/Contact.cs +++ b/SharpRepository.Tests/TestObjects/Contact.cs @@ -17,5 +17,26 @@ public class Contact public ContactType ContactType { get; set; } public byte[] Image { get; set; } + + public override bool Equals(object obj) + { + var contact = obj as Contact; + + return GetHashCode() == contact.GetHashCode(); + } + + public override int GetHashCode() + { + var hashCode = -1084825632; + hashCode = hashCode * -1521134295 + ContactId.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Title); + hashCode = hashCode * -1521134295 + ContactTypeId.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(EmailAddresses); + hashCode = hashCode * -1521134295 + EqualityComparer>.Default.GetHashCode(PhoneNumbers); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ContactType); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Image); + return hashCode; + } } } \ No newline at end of file diff --git a/SharpRepository.Tests/TestObjects/ContactType.cs b/SharpRepository.Tests/TestObjects/ContactType.cs index 2bb804f3..be4dbe31 100644 --- a/SharpRepository.Tests/TestObjects/ContactType.cs +++ b/SharpRepository.Tests/TestObjects/ContactType.cs @@ -1,9 +1,26 @@ -namespace SharpRepository.Tests.TestObjects +using System.Collections.Generic; + +namespace SharpRepository.Tests.TestObjects { public class ContactType { public int ContactTypeId { get; set; } public string Name { get; set; } public string Abbreviation { get; set; } + + public override bool Equals(object obj) + { + var cotactType = obj as ContactType; + return GetHashCode() == obj.GetHashCode(); + } + + public override int GetHashCode() + { + var hashCode = -591342771; + hashCode = hashCode * -1521134295 + ContactTypeId.GetHashCode(); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Abbreviation); + return hashCode; + } } }