Complicated grouping on multiple properties issue - c#

I have a large amount of directory type data, with properties like email, telephone, post code etc. I want to detect and group duplicates but on all the multiple properties, where groups can be made up of duplicates on different properties but not all have to be direct duplicates of each other but may be related via another row.
Example data:
Name | Email | Tel | Postcode
John Sim | j#j.com | 111 | C67
J Sim | | 111 |
John S | | | C67
I'd like to be able to find a way that I can detect duplicates over the Email, Tel and Postcode properties but even if they don't all match. So in the example above I'd get all 3 of the above grouped, even though the last one has no directed match to the middle one, but the first shares a match with both.
If that makes sense! obviously that is very simplified, I have a 100's of records I'm trying to group in a way I can display the duplicate groups.
So far I have found some very inefficient ways of doing this, involving going through each row, grouping any direct duplicates, but then checking to see if any of those duplicates also have duplicates and if so move them all together into a new group. But I'm trying to find some inspiration and a more efficient way of doing it :)
Thanks!

You could get the complexity down to O(n) using indexes in a foreach loop and combining on every iteration like the following:
foreach (var entry in list)
{
Group emailGroup = null;
Group telGroup = null;
Group postcodeGroup = null;
if (entry.Email != null && _emailGroups.TryGetValue(entry.Email, out emailGroup))
if (!emailGroup.Add(entry)) emailGroup = null;
if (entry.Tel != null && _telGroups.TryGetValue(entry.Tel, out telGroup))
if (!telGroup.Add(entry)) telGroup = null;
if (entry.Postcode != null && _postcodeGroups.TryGetValue(entry.Postcode, out postcodeGroup))
if (!postcodeGroup.Add(entry)) postcodeGroup = null;
if (emailGroup == null && telGroup == null && postcodeGroup == null)
{
CreateGroup(entry);
continue;
}
CombineGroups(emailGroup, telGroup, postcodeGroup);
}
Of course, you would have to decide + handle what you want to do on collision etc. and if need be, add any Name logic (e.g. Split First Name + Middle + Last) and then do a 2 way Contains on each (quite expensive so might want to look at string indexes)
Full Code + Test
See Method
[Test]
public void Test()
in the following.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using NUnit.Framework;
namespace StackOverflow
{
[TestFixture]
public class Class1
{
private Dictionary<string, Group> _emailGroups;
private Dictionary<string, Group> _telGroups;
private Dictionary<string, Group> _postcodeGroups;
private void CreateGroup(Entry entry)
{
var group = new Group(entry);
if (group.Email != null && !_emailGroups.ContainsKey(group.Email))
_emailGroups[group.Email] = group;
if (group.Tel != null && !_emailGroups.ContainsKey(group.Tel))
_telGroups[group.Tel] = group;
if (group.PostCode != null && !_emailGroups.ContainsKey(group.PostCode))
_postcodeGroups[group.PostCode] = group;
}
private void CombineGroups(Group emailGroup, Group telGroup, Group postcodeGroup)
{
if (emailGroup != telGroup && emailGroup != null && telGroup != null)
{
if (emailGroup.CanCombine(telGroup))
{
emailGroup.Add(telGroup);
UpdateIndexes(emailGroup, telGroup);
telGroup = null;
}
;
}
if (emailGroup != postcodeGroup && emailGroup != null && postcodeGroup != null)
{
if (emailGroup.CanCombine(postcodeGroup))
{
emailGroup.Add(postcodeGroup);
UpdateIndexes(emailGroup, postcodeGroup);
postcodeGroup = null;
}
;
}
if (telGroup != postcodeGroup && telGroup != null && postcodeGroup != null)
{
if (telGroup.CanCombine(postcodeGroup))
{
telGroup.Add(postcodeGroup);
UpdateIndexes(telGroup, postcodeGroup);
postcodeGroup = null;
}
;
}
}
private void UpdateIndexes(Group newGroup, Group oldGroup)
{
Group group;
if (oldGroup.Email != null
&& _emailGroups.TryGetValue(oldGroup.Email, out group)
&& group == oldGroup)
_emailGroups[oldGroup.Email] = newGroup;
if (oldGroup.Tel != null
&& _telGroups.TryGetValue(oldGroup.Tel, out group)
&& group == oldGroup)
_telGroups[oldGroup.Tel] = newGroup;
if (oldGroup.PostCode != null
&& _postcodeGroups.TryGetValue(oldGroup.PostCode, out group)
&& group == oldGroup)
_postcodeGroups[oldGroup.PostCode] = newGroup;
}
public class Group
{
public HashSet<Entry> Entries = new HashSet<Entry>();
public Group(Entry entry)
{
Email = entry.Email;
Tel = entry.Tel;
PostCode = entry.PostCode;
Entries.Add(entry);
}
public string Email { get; set; }
public string Tel { get; set; }
public string PostCode { get; set; }
public bool Matches(Entry entry)
{
if (Email != null && entry.Email != null && entry.Email != Email)
return false;
if (Tel != null && entry.Tel != null && entry.Tel != Tel)
return false;
if (PostCode != null && entry.PostCode != null && entry.PostCode != PostCode)
return false;
return true;
}
public bool Add(Entry entry)
{
if (!Matches(entry))
return false;
Entries.Add(entry);
if (Email == null && entry.Email != null)
Email = entry.Email;
if (Tel == null && entry.Tel != null)
Tel = entry.Tel;
if (PostCode == null && entry.PostCode != null)
PostCode = entry.PostCode;
return true;
}
public bool CanCombine(Group entry)
{
if (Email != null && entry.Email != null && Email != entry.Email)
return false;
if (Tel != null && entry.Tel != null && Tel != entry.Tel)
return false;
if (PostCode != null && entry.PostCode != null && PostCode != entry.PostCode)
return false;
return true;
}
public void Add(Group group)
{
foreach (var entry in group.Entries)
{
Add(entry);
}
}
public override string ToString()
{
var sb = new StringBuilder();
sb.AppendLine($"Key: {Email ?? "null"} | {Tel ?? "null"} | {PostCode ?? "null"}");
foreach (var entry in Entries)
{
sb.AppendLine(entry.ToString());
}
return sb.ToString();
}
}
public class Entry
{
public Entry(string name, string email, string tel, string postCode)
{
Name = name;
Email = email;
Tel = tel;
PostCode = postCode;
}
public string Name { get; set; }
public string Email { get; set; }
public string Tel { get; set; }
public string PostCode { get; set; }
public override string ToString()
{
return $"Entry: {Name ?? "null"} | {Email ?? "null"} | {Tel ?? "null"} | {PostCode ?? "null"}";
}
}
[Test]
public void Test()
{
var list = new List<Entry>
{
new Entry("John S", null, null, "C67"),
new Entry("J Sim", null, "111", null),
new Entry("John Sim", "j#j.com", "111", "C67")
};
_emailGroups = new Dictionary<string, Group>();
_telGroups = new Dictionary<string, Group>();
_postcodeGroups = new Dictionary<string, Group>();
foreach (var entry in list)
{
Group emailGroup = null;
Group telGroup = null;
Group postcodeGroup = null;
if (entry.Email != null && _emailGroups.TryGetValue(entry.Email, out emailGroup))
if (!emailGroup.Add(entry)) emailGroup = null;
if (entry.Tel != null && _telGroups.TryGetValue(entry.Tel, out telGroup))
if (!telGroup.Add(entry)) telGroup = null;
if (entry.PostCode != null && _postcodeGroups.TryGetValue(entry.PostCode, out postcodeGroup))
if (!postcodeGroup.Add(entry)) postcodeGroup = null;
if (emailGroup == null && telGroup == null && postcodeGroup == null)
{
CreateGroup(entry);
continue;
}
CombineGroups(emailGroup, telGroup, postcodeGroup);
}
var groups = _emailGroups.Select(x => x.Value)
.Union(_telGroups.Select(x => x.Value))
.Union(_postcodeGroups.Select(x => x.Value))
.Distinct()
.ToList();
foreach (var grp in groups)
{
Console.WriteLine(grp.ToString());
}
groups.Should().HaveCount(1);
groups.First().Entries.Should().HaveCount(3);
}
}
}

Related

Make Filter by one or more parameters

I have List<Book> Books
public class Book
{
public string Name { get; set; }
public string Author { get; set; }
public string Category { get; set; }
public string Language { get; set; }
public DateTime Publication_date { get; set; }
public string ISBN { get; set; }
}
tried this, but it only work if all parameters matched
foreach (var item in Books)
{
if ((!string.IsNullOrEmpty(name) && item.Name == name)
&& (!string.IsNullOrEmpty(author) && item.Author == author)
&& (!string.IsNullOrEmpty(category) && item.Category == category)
&& (!string.IsNullOrEmpty(language) && item.Language == language)
&& (Publication_date.HasValue && item.Publication_date == Publication_date)
&& (!string.IsNullOrEmpty(ISBN) && item.ISBN == ISBN))
{
Console.WriteLine(item);
}
}
how to make filter by one or more parameters? like if i enter only "name" and "Language" it will print me the book where both parameters a matched (if string is null or empty then parameter should be skipped)
Thanks)
The logic is a touch complicated, but made simpler by use of continue.
foreach (var item in Books)
{
if (!string.IsNullOrEmpty(name) && item.Name != name) continue;
if (!string.IsNullOrEmpty(author) && item.Author != author) continue;
if (!string.IsNullOrEmpty(category) && item.Category != category) continue;
if (!string.IsNullOrEmpty(language) && item.Language != language) continue;
if (Publication_date.HasValue && item.Publication_Date != Publication_Date) continue;
if (!string.IsNullOrEmpty(ISBN) && item.ISBN!= ISBN) continue;
Console.WriteLine(item);
}
you want it in a form where each condition returns true if null, or true if match. Basically null/empty means anything is a match
like
(string.IsNullOrEmpty(name) || item.Name == name)
then && them altogether
foreach (var item in Books)
{
if ((string.IsNullOrEmpty(name) || item.Name == name)
&& (string.IsNullOrEmpty(author) || item.Author == author)
&& (string.IsNullOrEmpty(category) || item.Category == category)
&& (string.IsNullOrEmpty(language) || item.Language == language)
&& (!Publication_date.HasValue || item.Publication_date == Publication_date)
&& (string.IsNullOrEmpty(ISBN) || item.ISBN == ISBN))
{
Console.WriteLine(item);
}
}
to be a little clearer you can do
bool IsMatchForFilter(string filter, string value)
=> string.IsNullOrEmpty(filter) || filter == value;
then do
if(
IsMatchForFilter(name, item.Name)
&& IsMatchForFilter(author, item.Author)
...
Try it like this, use the OR (||) operator
foreach (var item in Books)
{
if ((!string.IsNullOrEmpty(name) && item.Name == name)
|| (!string.IsNullOrEmpty(author) && item.Author == author)
|| (!string.IsNullOrEmpty(category) && item.Category == category)
|| (!string.IsNullOrEmpty(language) && item.Language == language)
|| (Publication_date.HasValue && item.Publication_date == Publication_date)
|| (!string.IsNullOrEmpty(ISBN) && item.ISBN == ISBN))
{
Console.WriteLine(item);
}
}

Linq to entities: finding matches

I have these two EF classes:
class Row
{
public long CategoryId { get; set; }
public virtual Category Category { get; set; }
public long VesselId { get; set; }
public virtual Vessel Vessel { get; set; }
public int TruckType { get; set; }
}
class RowFilter
{
public long? CategoryId { get; set; }
public virtual Category Category { get; set; }
public long? VesselId { get; set; }
public virtual Vessel Vessel { get; set; }
public int? TruckType { get; set; }
public long? PortId { get; set; }
public virtual Port Port { get; set; }
public bool IsMatch(Row row)
{
if (CategoryId == null || CategoryId == row.CategoryId) {
if (VesselId == null || VesselId == row.VesselId) {
if (TruckType == null || TruckType == row.TruckType) {
if (PortId == null || PortId == row.Vessel.PortId) {
return true;
}
}
}
}
return false;
}
}
That is:
A Filter matches a Row if IsMatch() returns true for that row.
I have a list of rows, in an IQueryable manner:
var rows = dbContext.Rows.AsQueryable().Where(...);
...and for each row, I want to select (project) the row itself and the list of filters that match this row. I can do this easily in a Linq-to-Objects way ("in memory"):
// Linq-to-objects
rows.ToList().Select(r => new
{
row = r,
filters = dbContext.RowsFilters.Where(f => f.IsMatch(r))
};
Question is... is it possible to do it with Linq-to-Entities? (sql, not "in memory")
In a static world, I would have these navigation properties:
class Row
{
...
public virtual List<RowFilter> RowFilters { get; set; }
}
class RowFilter
{
...
public virtual List<Rows> Rows { get; set; }
}
but... that means a lot of updating: when creating a new RowFilter, when creating a new Row, etc.
You can do the following steps:
Modify the IsMatch method to return a Expression<Func<Row, bool>> type and implement it like this :
public Expression<Func<Row, bool>> IsMatch()
{
Expression<Func<Row, bool>> filter = r => (CategoryId == null || CategoryId == r.CategoryId)
&& (VesselId == null || VesselId == r.VesselId)
&& (TruckType == null || TruckType == r.TruckType)
&& (PortId == null || PortId == r.PortId);
return filter;
}
Then just use it like this :
var rowFilter = new RowFilter { PortId = 1, CategoryId = 2, TruckType = 3, VesselId = 4 };
var query = context.Rows.Where(rowFilter.IsMatch());
All the linq are translated into SQL then executed on the server side. The generated SQL by EF looks like the following:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[CategoryId] AS [CategoryId],
[Extent1].[VesselId] AS [VesselId],
[Extent1].[TruckType] AS [TruckType],
[Extent1].[PortId] AS [PortId]
FROM [dbo].[Rows] AS [Extent1]
WHERE (#p__linq__0 IS NULL OR #p__linq__1 = [Extent1].[CategoryId]) AND (#p__linq__2 IS NULL OR #p__linq__3 = [Extent1].[VesselId]) AND (#p__linq__4 IS NULL OR #p__linq__5 = [Extent1].[TruckType]) AND (#p__linq__6 IS NULL OR #p__linq__7 = CAST( [Extent1].[PortId] AS bigint))
You can use the following query:
var query = from r in context.Rows
from f in context.RowFilters.Where(f =>
(f.CategoryId == null || f.CategoryId == r.CategoryId) &&
(f.VesselId == null || f.VesselId == r.VesselId) &&
(f.TruckType == null || f.TruckType == r.TruckType) &&
(f.PortId == null || f.PortId == r.Vessel.PortId))
.DefaultIfEmpty()
let x = new {r, f}
group x by x.r
into gr
select new
{
row = gr.Key,
filters = gr.Select(y => y.f).Where(yf => yf != null)
};
var result = query.ToList();
Here is an alternative syntax:
var query = context.Rows
.SelectMany(r =>
context.RowFilters.Where(f =>
(f.CategoryId == null || f.CategoryId == r.CategoryId) &&
(f.VesselId == null || f.VesselId == r.VesselId) &&
(f.TruckType == null || f.TruckType == r.TruckType) &&
(f.PortId == null || f.PortId == r.Vessel.PortId))
.DefaultIfEmpty()
.Select(f => new {r, f}))
.GroupBy(x => x.r)
.Select(x => new
{
row = x.Key,
filters = x.Select(y => y.f).Where(yf => yf != null)
});

How to get the last row Id after insert data into table for SQLite [duplicate]

This question already has answers here:
How to get last insert Id in SQLite?
(6 answers)
Closed 9 years ago.
I am using SQLite and SQLite-Net Wrapper for WinRT app. Other platform may have SQLite, but the implementation may be different such as using SQLite-Net api.
How do I get the last row Id immediately after insert for SQLite? Thanks
using (var db = new SQLite.SQLiteConnection(DBPath))
{
var newOrder = new SalesOrder()
{
CustId = g_intCustId,
Customer_No = txtBlkCustomer.Text.Trim(),
Order_Date = DateTime.Today
};
db.Insert(newOrder);
}
--1--- Update : I am using SQLite-Net Wrapper. I am not using SQLite -WInRT
I get the following error :
The type arguments for method 'SQLite.SQLiteConnection.ExecuteScalar(string, params object[])'
cannot be inferred from the usage. Try specifying the type arguments explicitly.
db.Insert(newOrder);
var key = db.ExecuteScalar("SELECT last_insert_rowid()");
---2-- Update
This is the class :
My problem is : How to get the SId immediately after inserting a record using above code.
class SalesOrder
{
[PrimaryKey, AutoIncrement]
public int SId { get; set; }
public int CustId { get; set; }
public string Customer_No { get; set; }
public DateTime Order_Date { get; set; }
}
do you have the ExecuteScalar method on your connection? then use
var key = db.ExecuteScalar<int>("SELECT last_insert_rowid()");
In SQLite-net, Insert method returns the number of row inserted (SQLite.cs). So if you want it to return the last row ID you can update it to do like that.
Current implementation.
public int Insert (object obj, string extra, Type objType)
{
if (obj == null || objType == null) {
return 0;
}
var map = GetMapping (objType);
#if NETFX_CORE
if (map.PK != null && map.PK.IsAutoGuid)
{
// no GetProperty so search our way up the inheritance chain till we find it
PropertyInfo prop;
while (objType != null)
{
var info = objType.GetTypeInfo();
prop = info.GetDeclaredProperty(map.PK.PropertyName);
if (prop != null)
{
if (prop.GetValue(obj, null).Equals(Guid.Empty))
{
prop.SetValue(obj, Guid.NewGuid(), null);
}
break;
}
objType = info.BaseType;
}
}
#else
if (map.PK != null && map.PK.IsAutoGuid) {
var prop = objType.GetProperty(map.PK.PropertyName);
if (prop != null) {
if (prop.GetValue(obj, null).Equals(Guid.Empty)) {
prop.SetValue(obj, Guid.NewGuid(), null);
}
}
}
#endif
var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0;
var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns;
var vals = new object[cols.Length];
for (var i = 0; i < vals.Length; i++) {
vals [i] = cols [i].GetValue (obj);
}
var insertCmd = map.GetInsertCommand (this, extra);
var count = insertCmd.ExecuteNonQuery (vals);
if (map.HasAutoIncPK)
{
var id = SQLite3.LastInsertRowid (Handle);
map.SetAutoIncPK (obj, id);
}
return count;
}
Updated implementation.
public int Insert (object obj, string extra, Type objType)
{
if (obj == null || objType == null) {
return 0;
}
var map = GetMapping (objType);
#if NETFX_CORE
if (map.PK != null && map.PK.IsAutoGuid)
{
// no GetProperty so search our way up the inheritance chain till we find it
PropertyInfo prop;
while (objType != null)
{
var info = objType.GetTypeInfo();
prop = info.GetDeclaredProperty(map.PK.PropertyName);
if (prop != null)
{
if (prop.GetValue(obj, null).Equals(Guid.Empty))
{
prop.SetValue(obj, Guid.NewGuid(), null);
}
break;
}
objType = info.BaseType;
}
}
#else
if (map.PK != null && map.PK.IsAutoGuid) {
var prop = objType.GetProperty(map.PK.PropertyName);
if (prop != null) {
if (prop.GetValue(obj, null).Equals(Guid.Empty)) {
prop.SetValue(obj, Guid.NewGuid(), null);
}
}
}
#endif
var replacing = string.Compare (extra, "OR REPLACE", StringComparison.OrdinalIgnoreCase) == 0;
var cols = replacing ? map.InsertOrReplaceColumns : map.InsertColumns;
var vals = new object[cols.Length];
for (var i = 0; i < vals.Length; i++) {
vals [i] = cols [i].GetValue (obj);
}
var insertCmd = map.GetInsertCommand (this, extra);
var count = insertCmd.ExecuteNonQuery (vals);
long id = 0; //New line
if (map.HasAutoIncPK)
{
id = SQLite3.LastInsertRowid (Handle); //Updated line
map.SetAutoIncPK (obj, id);
}
//Updated lines
//return count; //count is row affected, id is primary key
return (int)id;
//Updated lines
}

Making A Function Recursive

the following function needs to look inside the input object if there is a property in it that returns a custom object it needs to do the trimming of that object as well. The code below works for the input object fine, but wont recursively look into a property that returns a custom object and do the trimming process.
public object TrimObjectValues(object instance)
{
var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore non-string properties
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType == typeof(object))
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else if (prop.PropertyType == typeof(object))
{
TrimObjectValues(prop);
}
}
return instance;
}
I need to change this somehow to look for other objects inside the initial object
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType == typeof(object))
This code isn't working reason is for a example is the object I am passing as input has a property that returns a type of "Address" therefore typeof(object) never gets hit.
Here is a tree of data to test against pass the function "o" in this case
Order o = new Order();
o.OrderUniqueIdentifier = "TYBDEU83e4e4Ywow";
o.VendorName = "Kwhatever";
o.SoldToCustomerID = "Abc98971";
o.OrderType = OrderType.OnOrBefore;
o.CustomerPurchaseOrderNumber = "MOOMOO 56384";
o.EmailAddress = "abc#electric.com";
o.DeliveryDate = DateTime.Now.AddDays(35);
Address address1 = new Address();
//address1.AddressID = "Z0mmn01034";
address1.AddressID = "E0000bbb6 ";
address1.OrganizationName = " Nicks Organization ";
address1.AddressLine1 = " 143 E. WASHINGTON STREET ";
address1.City = " Rock ";
address1.State = "MA ";
address1.ZipCode = " 61114";
address1.Country = "US ";
o.ShipToAddress = address1;
Your tests with typeof(object) will all fail.
Try like this:
static void TrimObjectValues(object instance)
{
// if the instance is null we have nothing to do here
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
// if we have a string property we trim it
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
// if we don't have a string property we recurse
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
I have also made the function return no value because you are modifying the argument instance anyway.
Test case:
public enum OrderType
{
OnOrBefore
}
public class Order
{
public string OrderUniqueIdentifier { get; set; }
public string VendorName { get; set; }
public string SoldToCustomerID { get; set; }
public OrderType OrderType { get; set; }
public string CustomerPurchaseOrderNumber { get; set; }
public string EmailAddress { get; set; }
public DateTime DeliveryDate { get; set; }
public Address ShipToAddress { get; set; }
}
public class Address
{
public string AddressID { get; set; }
public string OrganizationName { get; set; }
public string AddressLine1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
}
class Program
{
static void Main()
{
Order o = new Order();
o.OrderUniqueIdentifier = "TYBDEU83e4e4Ywow";
o.VendorName = "Kwhatever";
o.SoldToCustomerID = "Abc98971";
o.OrderType = OrderType.OnOrBefore;
o.CustomerPurchaseOrderNumber = "MOOMOO 56384";
o.EmailAddress = "abc#electric.com";
o.DeliveryDate = DateTime.Now.AddDays(35);
Address address1 = new Address();
//address1.AddressID = "Z0mmn01034";
address1.AddressID = "E0000bbb6 ";
address1.OrganizationName = " Nicks Organization ";
address1.AddressLine1 = " 143 E. WASHINGTON STREET ";
address1.City = " Rock ";
address1.State = "MA ";
address1.ZipCode = " 61114";
address1.Country = "US ";
o.ShipToAddress = address1;
TrimObjectValues(o);
}
static void TrimObjectValues(object instance)
{
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
}
UPDATE 2:
It seems that you want to handle also lists of objects. You could adapt the method:
static void TrimObjectValues(object instance)
{
if (instance == null)
{
return;
}
var props = instance
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
if (instance is IEnumerable)
{
foreach (var element in (IEnumerable)instance)
{
TrimObjectValues(element);
}
return;
}
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
}
prop.PropertyType == typeof(object) does not work, because this will only be true for object and not for derived types. You would have to write typeof(object).IsAssignableFrom(prop.PropertyType); however, this applies for all the types! Drop both conditions (for string and for object).
Note: Also drop the condition before the TrimObjectValues(prop);. (Replace else if (...) by else)
public object TrimObjectValues(object instance)
{
if (instance is string)
{
instance = ((string)instance).Trim();
return instance;
}
if (instance is IEnumerable)
{
foreach (var element in (IEnumerable)instance)
{
TrimObjectValues(element);
}
return instance;
}
var props = instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
// Ignore non-string properties
.Where(prop => prop.PropertyType == typeof(string) | prop.PropertyType is object)
// Ignore indexers
.Where(prop => prop.GetIndexParameters().Length == 0)
// Must be both readable and writable
.Where(prop => prop.CanWrite && prop.CanRead);
foreach (PropertyInfo prop in props)
{
if (prop.PropertyType == typeof(string))
{
string value = (string)prop.GetValue(instance, null);
if (value != null)
{
value = value.Trim();
prop.SetValue(instance, value, null);
}
}
else if (prop.PropertyType is object)
{
TrimObjectValues(prop.GetValue(instance, null));
}
}
return instance;
}

C# Object reference not set to an instance of an object

I have the following code:
public void SetUser(User user)
{
string streetNumber = "";
if (user.Address.StreetNo != null)
streetNumber = user.Address.StreetNo.ToString();
else
streetNumber = "";
}
I get the ever popular
Object reference not set to an instance of an object.
issue.
public void SetUser(User user)
{
string streetNumber = "";
if (user != null && user.Address != null && user.Address.StreetNo != null) {
streetNumber = user.Address.StreetNo.ToString();
}
}
Taking into account #CKoenig's suggestion, the following throws an exception if the user or user.Address are null:
public void SetUser(User user)
{
if (user == null) {
throw new System.ArgumentNullException("user", "user cannot be null");
}
if (user.Address == null) {
throw new System.ArgumentNullException("Address", "Address cannot be null");
}
string streetNumber = "";
if (user.Address.StreetNo != null) {
streetNumber = user.Address.StreetNo.ToString();
}
}
public void SetUser(User user)
{
string streetNumber = String.Empty;
if (user!= null
&& user.Address != null
&& user.Address.StreetNo != null)
{
streetNumber = user.Address.StreetNo.ToString();
}
}
Either user is null, or user.Address is null. You need to test them too.
Check your stacktrace and:
user
user.Address
user.Address.StreetNo
with an if ... == null then ...
if (user != null
&& user.Address != null
&& user.Address.StreetNo != null)
{
// ...
}

Categories