Serializing a DataSet/DataTable with properties in json.net - dataset

I'm looking a solution to serialize dataset to json, but I need to get the rowstate in json.
Does json.net serialize / deserialize properties of the dataset / datatable, like rowstate? I can only find examples with row value.
Thanks.

No, the DataTableConverter that ships with Json.Net does not serialize the RowState. If you really need this value in the JSON, it should be possible to create a custom JsonConverter to output it. However, it will not be possible to deserialize the RowState back to its original value due to the fact that it is a read-only property. (In fact, this value is calculated from a variety of internal state variables.)

I wrote a custom converter for DataSet which keeps the row state. The DataSet class can write the schema as xml with WriteXmlSchema, and it can write the data including the rowstate with WriteXml(sw, XmlWriteMode.DiffGram). This produces two strings that are properties in the DataSetSerializer class, which then can be serialized and deserialized with JsonSerializer. Here is the code in a test class:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Data;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace TestProject3
{
public class DataSetConverter : JsonConverter<DataSet>
{
private class DataSetSerializer
{
public string SchemaString { get; set; }
public string DataString { get; set; }
private static string GetSchema(DataSet ds)
{
using (var sw = new StringWriter())
{
ds.WriteXmlSchema(sw);
return sw.ToString();
}
}
private static string GetData(DataSet ds)
{
using (var sw = new StringWriter())
{
ds.WriteXml(sw, XmlWriteMode.DiffGram);
return sw.ToString();
}
}
private DataSet GetDataSet()
{
var ds = new DataSet();
using (var sr1 = new StringReader(SchemaString))
{
ds.ReadXmlSchema(sr1);
}
using (var sr2 = new StringReader(DataString))
{
ds.ReadXml(sr2, XmlReadMode.DiffGram);
}
return ds;
}
public static string Serialize(DataSet ds)
{
var serializer = new DataSetSerializer() { SchemaString = GetSchema(ds), DataString = GetData(ds) };
return JsonSerializer.Serialize<DataSetSerializer>(serializer);
}
public static DataSet DeSerialize(string s)
{
var serializer = JsonSerializer.Deserialize<DataSetSerializer>(s);
return serializer.GetDataSet();
}
}
public override DataSet Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DataSetSerializer.DeSerialize(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DataSet value, JsonSerializerOptions options)
{
writer.WriteStringValue(DataSetSerializer.Serialize(value));
}
}
[TestClass]
public class TestDataSet
{
private DataSet CreateTestDataSet()
{
var ds = new DataSet();
var dt = ds.Tables.Add();
dt.Columns.Add("A");
dt.Columns.Add("B");
dt.Rows.Add("A1", "B1");
var dr = dt.Rows.Add("A2", "B2");
ds.AcceptChanges();
dr["A"] = "AA2";
return ds;
}
private void CheckTestDataSet(DataSet ds)
{
var dt = ds.Tables[0];
Assert.IsTrue(dt.Rows[0].RowState == DataRowState.Unchanged && dt.Rows[1].RowState == DataRowState.Modified);
}
[TestMethod]
public void TestDataSetConverter()
{
var ds = CreateTestDataSet();
var serializeOptions = new JsonSerializerOptions { Converters = { new DataSetConverter() } };
var jsonstring = JsonSerializer.Serialize(ds, serializeOptions);
ds = JsonSerializer.Deserialize<DataSet>(jsonstring, serializeOptions);
CheckTestDataSet(ds);
}
}
}

I needed a solution that maintained RowState for DataSets and DataTables for a legacy app migrating from SOAP calls to WebApi calls but using Newtonsoft rather than System.Text.Json. Thank you Alex for the last answer which was just what I needed.
Here is a Newtonsoft version of the same thing:
using System;
using System.Data;
using System.IO;
using Newtonsoft.Json;
using Xunit;
namespace DataSetsAndTablesTester
{
public class CustomJsonConverter_DataSet : JsonConverter<DataSet>
{
private class DataSetSerializer
{
public string SchemaString { get; set; }
public string DataString { get; set; }
private static string GetSchema(DataSet ds)
{
using (var sw = new StringWriter())
{
ds.WriteXmlSchema(sw);
return sw.ToString();
}
}
private static string GetData(DataSet ds)
{
using (var sw = new StringWriter())
{
ds.WriteXml(sw, XmlWriteMode.DiffGram);
return sw.ToString();
}
}
private DataSet GetDataSet()
{
var ds = new DataSet();
using (var sr1 = new StringReader(SchemaString))
{
ds.ReadXmlSchema(sr1);
}
using (var sr2 = new StringReader(DataString))
{
ds.ReadXml(sr2, XmlReadMode.DiffGram);
}
return ds;
}
public static string Serialize(DataSet ds)
{
var serializer = new DataSetSerializer() { SchemaString = GetSchema(ds), DataString = GetData(ds) };
return JsonConvert.SerializeObject(serializer);
}
public static DataSet DeSerialize(string s)
{
var serializer = JsonConvert.DeserializeObject<DataSetSerializer>(s);
return serializer.GetDataSet();
}
}
public override void WriteJson(JsonWriter writer, DataSet value, JsonSerializer serializer)
{
if (value == null)
throw new Exception("Passed in DataSet is null");
writer.WriteValue(DataSetSerializer.Serialize(ds: value));
}
public override DataSet ReadJson(JsonReader reader, Type objectType, DataSet existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DataSetSerializer.DeSerialize((string)reader.Value);
}
}
public class CustomJsonConverter_DataTable : JsonConverter<DataTable>
{
private class DataTableSerializer
{
public string SchemaString { get; set; }
public string DataString { get; set; }
private static string GetSchema(DataTable dt)
{
using (var sw = new StringWriter())
{
dt.WriteXmlSchema(sw);
return sw.ToString();
}
}
private static string GetData(DataTable dt)
{
using (var sw = new StringWriter())
{
dt.WriteXml(sw, XmlWriteMode.DiffGram);
return sw.ToString();
}
}
private DataTable GetDataTable()
{
var dt = new DataTable();
using (var sr1 = new StringReader(SchemaString))
{
dt.ReadXmlSchema(sr1);
}
using (var sr2 = new StringReader(DataString))
{
dt.ReadXml(sr2);
}
return dt;
}
public static string Serialize(DataTable dt)
{
var serializer = new DataTableSerializer() { SchemaString = GetSchema(dt), DataString = GetData(dt) };
return JsonConvert.SerializeObject(serializer);
}
public static DataTable DeSerialize(string s)
{
var serializer = JsonConvert.DeserializeObject<DataTableSerializer>(s);
return serializer.GetDataTable();
}
}
public override void WriteJson(JsonWriter writer, DataTable value, JsonSerializer serializer)
{
if (value == null)
throw new Exception("Passed in DataTable is null");
if (string.IsNullOrEmpty(value.TableName))
throw new Exception("Passed in DataTable Name is null or empty");
writer.WriteValue(DataTableSerializer.Serialize(dt: value));
}
public override DataTable ReadJson(JsonReader reader, Type objectType, DataTable existingValue, bool hasExistingValue, JsonSerializer serializer)
{
return DataTableSerializer.DeSerialize((string)reader.Value);
}
}
public class JsonConverterTester
{
public void TestJsonDataSetConverter()
{
DataSet ds = CreateTestDataSet();
String json = JsonConvert.SerializeObject(value: ds, converters: new CustomJsonConverter_DataSet());
Console.WriteLine(json);
DataSet ds2 = JsonConvert.DeserializeObject<DataSet>(value: json, converters: new CustomJsonConverter_DataSet());
CheckTestDataSet(ds2);
}
public void TestJsonDataTableConverter()
{
DataTable dt = CreateTestDataTable();
String json = JsonConvert.SerializeObject(value: dt, converters: new CustomJsonConverter_DataTable());
Console.WriteLine(json);
DataTable dt2 = JsonConvert.DeserializeObject<DataTable>(value: json, converters: new CustomJsonConverter_DataTable());
CheckTestDataTable(dt2);
}
private DataSet CreateTestDataSet()
{
var ds = new DataSet();
var dt = ds.Tables.Add();
dt.Columns.Add("A");
dt.Columns.Add("B");
dt.Rows.Add("A1", "B1");
var dr = dt.Rows.Add("A2", "B2");
ds.AcceptChanges();
dr["A"] = "AA2";
return ds;
}
private void CheckTestDataSet(DataSet ds)
{
var dt = ds.Tables[0];
Assert.True(dt.Rows[0].RowState == DataRowState.Unchanged && dt.Rows[1].RowState == DataRowState.Modified);
}
private DataTable CreateTestDataTable()
{
var dt = new DataTable();
dt.TableName = "TestTable";
dt.Columns.Add("A");
dt.Columns.Add("B");
dt.Rows.Add("A1", "B1");
var dr = dt.Rows.Add("A2", "B2");
dt.AcceptChanges();
dr["A"] = "AA2";
return dt;
}
private void CheckTestDataTable(DataTable dt)
{
Assert.True(dt.Rows[0].RowState == DataRowState.Unchanged && dt.Rows[1].RowState == DataRowState.Modified);
}
}
}

Related

.net c# WebChannelFactory keepAliveEnabled=false with webHttpBehavior

I create WebChannelFactory. I need set KeepAlive=false.
I found only one solution use CustomBinding. and set property keepAliveEnabled to false.
I use custom behavior for my factory also.
Code:
static CustomBinding GetBinding(MySettings serviceSettings = null)
{
var customBinding = new CustomBinding();
HttpTransportBindingElement transportBindingElement = new HttpTransportBindingElement();
transportBindingElement.KeepAliveEnabled = false;
transportBindingElement.MaxBufferSize = 0x07000000;
transportBindingElement.MaxReceivedMessageSize = 0x07000000;
if (serviceSettings != null)
{
customBinding.SendTimeout = serviceSettings.SendTimeout;
}
customBinding.Elements.Add(transportBindingElement);
return customBinding;
}
var customBinding = GetBinding(serviceSettings);
WebChannelFactory<TChannel> factory = new WebChannelFactory<TChannel>(customBinding, new Uri(url));
factory.Endpoint.Behaviors.Add(new MyWebHttpBehavior(userId));
class MyWebHttpBehavior : IEndpointBehavior
{
private int _userId;
public MyWebHttpBehavior(int userId)
{
_userId = userId;
}
public void AddBindingParameters(ServiceEndpoint serviceEndpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{ }
public void ApplyClientBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.ClientRuntime behavior)
{
behavior.MessageInspectors.Add(new MyClientMessageInspector(_userId));
}
public void ApplyDispatchBehavior(ServiceEndpoint serviceEndpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher)
{ }
public void Validate(ServiceEndpoint serviceEndpoint)
{ }
}
In current situation i get error "does not have a Binding with the None MessageVersion".

Virtualizing DataGrid over ListCollectionView

I am using a ListCollectionView over a custom list which provides read access to a particular database table. Below is the definition of the custom list.
class MaterialCollection : IList
{
#region Fields
private Object syncRoot;
private SQLiteConnection connection;
#endregion
#region IList Interface
public object this[int index]
{
get
{
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT * FROM Materials LIMIT 1 OFFSET #Index";
command.Parameters.AddWithValue("#Index", index);
using (SQLiteDataReader reader = command.ExecuteReader())
{
if (reader.Read())
{
return GetMaterial(reader);
}
else
{
throw new Exception();
}
}
}
}
set
{
throw new NotImplementedException();
}
}
public int Count
{
get
{
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT Count(*) FROM Materials";
return Convert.ToInt32(command.ExecuteScalar());
}
}
}
public bool IsFixedSize
{
get
{
return false;
}
}
public bool IsReadOnly
{
get
{
return true;
}
}
public bool IsSynchronized
{
get
{
return true;
}
}
public object SyncRoot
{
get
{
return this.syncRoot;
}
}
public IEnumerator GetEnumerator()
{
return Enumerate().GetEnumerator();
}
#endregion
#region Constructor
public MaterialCollection(SQLiteConnection connection)
{
this.connection = connection;
this.syncRoot = new Object();
}
#endregion
#region Private Methods
private Material GetMaterial(SQLiteDataReader reader)
{
int id = Convert.ToInt32(reader["Id"]);
string materialNumber = Convert.ToString(reader["MaterialNumber"]);
string type = Convert.ToString(reader["Type"]);
string description = Convert.ToString(reader["Description"]);
string alternateDescription = Convert.ToString(reader["AlternateDescription"]);
string tags = Convert.ToString(reader["Tags"]);
Material material = new Material();
material.Id = id;
material.MaterialNumber = materialNumber;
material.Type = (MaterialType)Enum.Parse(typeof(MaterialType), type);
material.Description = description;
material.AlternateDescription = alternateDescription;
material.Tags = tags;
return material;
}
private IEnumerable<Material> Enumerate()
{
using (SQLiteCommand command = connection.CreateCommand())
{
command.CommandText = "SELECT * FROM Materials";
using (SQLiteDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return GetMaterial(reader);
}
}
}
}
#endregion
#region Unimplemented Functions
public int Add(object value)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(object value)
{
throw new NotImplementedException();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public int IndexOf(object value)
{
return 0;
}
public void Insert(int index, object value)
{
throw new NotImplementedException();
}
public void Remove(object value)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
#endregion
}
The problem is that during the initial population of the DataGrid, the this[int index] method is called for every single row (i.e. 0 through Count - 1).
As the table contains potentially millions of rows and read operations are costly (i.e. SQLite on a legacy HDD), such an operation is unacceptable. Furthermore, it doesn't make sense to load all of the millions of rows when the screen can only accommodate for 50 or so items.
I would like to make it such that the DataGrid fetches only the row that it is displaying, instead of fetching the entire table. How is such a working possible?
(P.S. I have ensured that virtualizatin is enabled for the DataGrid itself)
It seems that the problem was resolved after adding ScrollViewer.CanContentScroll="True" to the ListView.

how to display files from ftp server to a local windows application gridview

i have uploaded my files to ftb server, now i want to display that files in my local windows application gridview
i want to display that files in datagridview.
public List<string> ListFiles()
{
// Get the object used to communicate with the server.
var request = (FtpWebRequest)WebRequest.Create("ftp://ipaddress/Requests/");
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
request.Credentials = new NetworkCredential("username", "password");
List<string> files = new List<string>();
using (var response = (FtpWebResponse)request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
var reader = new StreamReader(responseStream);
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line) == false)
{
files.Add(line.Split(new[] { ' ', '\t' }).Last());
}
}
return files;
}
}
}
following is the code on my load form.
FTPItility is my class in which listfiles is a method
FTPUtility obj = new FTPUtility();
List<string> strings = new List<string>();
dataGridViewRequest.DataSource = obj.ListFiles();
Here is the code you can use.
Here is code of FtpUtility:
public class FtpUtility
{
public string UserName { get; set; }
public string Password { get; set; }
public string Path { get; set; }
public List<string> ListFiles()
{
var request = (FtpWebRequest)WebRequest.Create(Path);
request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
request.Credentials = new NetworkCredential(UserName, Password);
List<string> files = new List<string>();
using (var response = (FtpWebResponse)request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
var reader = new StreamReader(responseStream);
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (string.IsNullOrWhiteSpace(line) == false)
{
var fileName = line.Split(new[] { ' ', '\t' }).Last();
if (!fileName.StartsWith("."))
files.Add(fileName);
}
}
return files;
}
}
}
}
And here is the code of form:
I have created an instance of FtpUtility and passed requiered parameters to it, then get the files and put it in a friendly list(Name, Path) and bind to grid:
private void Form1_Load(object sender, EventArgs e)
{
this.LoadFiles();
}
public void LoadFiles()
{
var ftp = new FtpUtility();
ftp.UserName = "username";
ftp.Password = "password";
ftp.Path = "ftp://address";
this.dataGridView1.DataSource = ftp.ListFiles()
.Select(x => new
{
Name = x, //Name Column
Path = ftp.Path + x //Path Column
}).ToList();
}

BindingList<> (master) with a composed BindingList<> (child) reference

I have a situation where a BindingList<> represents a collection of POCOs that have sub-collections of similar nature, Here is a sample code of two such POCOs and their respective lists:
The DirectoryTypePoco
public class DirectoryTypePoco : IBasePoco
{
public DirectoryTypePoco()
{
}
public DirectoryTypePoco(Int16 directoryTypeId, String directoryImplementation, String directoryDescription, DirectoryDefinitionPocoList directoryDefinition)
{
DirectoryTypeId = directoryTypeId;
DirectoryImplementation = directoryImplementation;
DirectoryDescription = directoryDescription;
DirectoryDefinition = directoryDefinition;
}
public Int16 DirectoryTypeId { get; set; }
public String DirectoryImplementation { get; set; }
public String DirectoryDescription { get; set; }
public DirectoryDefinitionPocoList DirectoryDefinition { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var lastMaxEntityId = repository.GetQuery<DirectoryType>().Select(select => #select.DirectoryTypeId).DefaultIfEmpty().Max();
var newEntity = new DirectoryType
{
DirectoryTypeId = (short)(lastMaxEntityId + 1),
DirectoryImplementation = this.DirectoryImplementation,
DirectoryDescription = this.DirectoryDescription
};
return newEntity;
}
}
And the BindingList<DirectoryTypePoco>:
public class DirectoryTypePocoList : BindingList<DirectoryTypePoco>
{
public DirectoryTypePocoList()
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>();
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
public DirectoryTypePocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryType>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryTypePoco(r.DirectoryTypeId, r.DirectoryImplementation, r.DirectoryDescription, new DirectoryDefinitionPocoList(r.DirectoryTypeId)));
}
}
}
}
The second object: DirectoryDefinitionPoco
public class DirectoryDefinitionPoco : IBasePoco
{
public DirectoryDefinitionPoco()
{
}
public DirectoryDefinitionPoco(Int16 directoryTypeId, Byte parameterId, String parameterName, String parameterValidation, Boolean encryptionRequired, PocoChangeType changeType = PocoChangeType.None)
{
DirectoryTypeId = directoryTypeId;
ParameterId = parameterId;
ParameterName = parameterName;
ParameterDescription = parameterName;
ParameterRequired = false;
ParameterValidation = parameterValidation;
EncryptionRequired = encryptionRequired;
}
public Int16 DirectoryTypeId { get; set; }
public Byte ParameterId { get; set; }
public String ParameterName { get; set; }
public String ParameterDescription { get; set; }
public String ParameterValidation { get; set; }
public Boolean ParameterRequired { get; set; }
public Boolean EncryptionRequired { get; set; }
public object GenerateEntity(GenericRepository repository, params object[] parameters)
{
var masterId = (short) parameters[0];
var lastMaxEntityId = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == masterId).Select(select => #select.ParameterId).DefaultIfEmpty().Max();
var newEntity = new DirectoryDefinition
{
DirectoryTypeId = (short)parameters[0],
ParameterId = (byte)(lastMaxEntityId + 1),
ParameterName = this.ParameterName,
ParameterDescription = this.ParameterDescription,
ParameterValidation = this.ParameterValidation,
ParameterRequired = this.ParameterRequired,
EncryptionRequired = this.EncryptionRequired
};
return newEntity;
}
}
And BindingList<DirectoryDefinitionPoco>:
public class DirectoryDefinitionPocoList : BindingList<DirectoryDefinitionPoco>
{
public DirectoryDefinitionPocoList(short directoryTypeId)
{
using (var repository = new GenericRepository(new PWRDbContext()))
{
var query = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId);
foreach (var r in query)
{
Add(new DirectoryDefinitionPoco(r.DirectoryTypeId, r.ParameterId, r.ParameterName, r.ParameterValidation, r.EncryptionRequired));
}
}
}
public List<DirectoryDefinition> GetSourceQuery()
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>().ToList();
}
return result;
}
public List<DirectoryDefinition> GetSourceQuery(short directoryTypeId)
{
List<DirectoryDefinition> result;
using (var repository = new GenericRepository(new PWRDbContext()))
{
result = repository.GetQuery<DirectoryDefinition>(where => where.DirectoryTypeId == directoryTypeId).ToList();
}
return result;
}
}
On the form, I load the data into the grid through a BindingSource component. The child rows are added properly and the data is valid.
Here is the issue: I'm able to add new DirectoryTypePoco but when try to add a DirectoryDefinitionPoco, in the code, the the DirectoryDefinitionPocoobject that I get has a zero for it's parent object. In the above picture, the Test5.dll234 is a DirectoryTypePoco with DirectoryTypeId = 8 and all child under it are ok except the new one I create. What am I suppose to do to make sure I have Master-Child relation in this case?
Ok. It seems that there are two thing I should have noticed in my design.
The individual child Poco needs to know the parent Poco through a reference.
The DevExpress Grid has methods that allow for retrieving the attached data to a parent row while in the child view' particular row.
The first part is straightforwards: add a new property in the child poco of parent poco type.
This however, in my case, doesn't solve my issue as when I visually add a new row on the grid, the default constructor is invoked and it takes no parameters and hence the parent poco reference will remain NULL and the Ids (numeric) will be defaulted to 0
The second point helped fix my issue completely. I was able to conjure up an extension method for the XtraGrid's GridView as follows:
public static class DevExpressGridHelper
{
public static IBasePoco GetPocoFromSelectedRow(this BaseView view)
{
return (IBasePoco)view.GetRow(((GridView)view).FocusedRowHandle);
}
public static IBasePoco GetParentPocoFromSelectedRow(this GridView view)
{
if (view.ParentView !=null)
{
// return (IBasePoco)(view.ParentView).GetRow(((GridView)(view.ParentView)).FocusedRowHandle);
return (IBasePoco)((GridView)view.ParentView).GetFocusedRow();
}
return null;
}
}
And used it as follows:
private void GridMain_Level_1_RowUpdated(object sender, RowObjectEventArgs e)
{
var view = sender as GridView;
if (view == null)
{
return;
}
var pocoObject = e.Row as DirectoryDefinitionPoco;
if (pocoObject == null)
{
return;
}
var parentPocoObject = view.GetParentPocoFromSelectedRow();
if (parentPocoObject == null)
{
return;
}
if (view.IsNewItemRow(e.RowHandle))
{
Create(pocoObject, parentPocoObject);
}
else
{
Update(pocoObject);
}
}

Filter a collection with LINQ vs CollectionView

I want to filter a ObservableCollection with max 3000 items in a DataGrid with 6 columns. The user should be able to filter in an "&&"-way all 6 columns.
Should I use LINQ or a CollectionView for it? LINQ seemed faster trying some www samples. Do you have any pro/cons?
UPDATE:
private ObservableCollection<Material> _materialList;
private ObservableCollection<Material> _materialListInternal;
public MaterialBrowserListViewModel()
{
_materialListInternal = new ObservableCollection<Material>();
for (int i = 0; i < 2222; i++)
{
var mat = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Excel Sheet" + i,
Keywords = "financial budget report",
SchoolclassCode = "1",
};
_materialListInternal.Add(mat);
var mat1 = new Material()
{
Schoolday = DateTime.Now.Date,
Period = i,
DocumentName = "Word Doc" + i,
Keywords = "Economical staticstics report",
SchoolclassCode = "2",
};
_materialListInternal.Add(mat1);
}
MaterialList = CollectionViewSource.GetDefaultView(MaterialListInternal);
MaterialList.Filter = new Predicate<object>(ContainsInFilter);
}
public bool ContainsInFilter(object item)
{
if (String.IsNullOrEmpty(FilterKeywords))
return true;
Material material = item as Material;
if (DocumentHelper.ContainsCaseInsensitive(material.Keywords,FilterKeywords,StringComparison.CurrentCultureIgnoreCase))
return true;
else
return false;
}
private string _filterKeywords;
public string FilterKeywords
{
get { return _filterKeywords; }
set
{
if (_filterKeywords == value)
return;
_filterKeywords = value;
this.RaisePropertyChanged("FilterKeywords");
MaterialList.Refresh();
}
}
public ICollectionView MaterialList { get; set; }
public ObservableCollection<Material> MaterialListInternal
{
get { return _materialListInternal; }
set
{
_materialListInternal = value;
this.RaisePropertyChanged("MaterialList");
}
}
Using ICollectionView gives you automatic collection changed notifications when you call Refresh. Using LINQ you'll need to fire your own change notifications when the filter needs to be re-run to update the UI. Not difficult, but requires a little more thought than just calling Refresh.
LINQ is more flexible that the simple yes/no filtering used by ICollectionView, but if you're not doing something complex there's not really any advantage to that flexibility.
As Henk stated, there shouldn't be a noticable performance difference in the UI.
For an interactive (DataGrid?) experience you should probabaly use the CollectionView. For a more code-oriented sorting, LINQ.
And with max 3000 items, speed should not be a (major) factor in a UI.
How about both? Thomas Levesque built a LINQ-enabled wrapper around ICollectionView.
Usage:
IEnumerable<Person> people;
// Using query comprehension
var query =
from p in people.ShapeView()
where p.Age >= 18
orderby p.LastName, p.FirstName
group p by p.Country;
query.Apply();
// Using extension methods
people.ShapeView()
.Where(p => p.Age >= 18)
.OrderBy(p => p.LastName)
.ThenBy(p => p.FirstName)
.Apply();
Code:
public static class CollectionViewShaper
{
public static CollectionViewShaper<TSource> ShapeView<TSource>(this IEnumerable<TSource> source)
{
var view = CollectionViewSource.GetDefaultView(source);
return new CollectionViewShaper<TSource>(view);
}
public static CollectionViewShaper<TSource> Shape<TSource>(this ICollectionView view)
{
return new CollectionViewShaper<TSource>(view);
}
}
public class CollectionViewShaper<TSource>
{
private readonly ICollectionView _view;
private Predicate<object> _filter;
private readonly List<SortDescription> _sortDescriptions = new List<SortDescription>();
private readonly List<GroupDescription> _groupDescriptions = new List<GroupDescription>();
public CollectionViewShaper(ICollectionView view)
{
if (view == null)
throw new ArgumentNullException("view");
_view = view;
_filter = view.Filter;
_sortDescriptions = view.SortDescriptions.ToList();
_groupDescriptions = view.GroupDescriptions.ToList();
}
public void Apply()
{
using (_view.DeferRefresh())
{
_view.Filter = _filter;
_view.SortDescriptions.Clear();
foreach (var s in _sortDescriptions)
{
_view.SortDescriptions.Add(s);
}
_view.GroupDescriptions.Clear();
foreach (var g in _groupDescriptions)
{
_view.GroupDescriptions.Add(g);
}
}
}
public CollectionViewShaper<TSource> ClearGrouping()
{
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearSort()
{
_sortDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> ClearFilter()
{
_filter = null;
return this;
}
public CollectionViewShaper<TSource> ClearAll()
{
_filter = null;
_sortDescriptions.Clear();
_groupDescriptions.Clear();
return this;
}
public CollectionViewShaper<TSource> Where(Func<TSource, bool> predicate)
{
_filter = o => predicate((TSource)o);
return this;
}
public CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> OrderByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, true, ListSortDirection.Descending);
}
public CollectionViewShaper<TSource> ThenBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Ascending);
}
public CollectionViewShaper<TSource> ThenByDescending<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
return OrderBy(keySelector, false, ListSortDirection.Descending);
}
private CollectionViewShaper<TSource> OrderBy<TKey>(Expression<Func<TSource, TKey>> keySelector, bool clear, ListSortDirection direction)
{
string path = GetPropertyPath(keySelector.Body);
if (clear)
_sortDescriptions.Clear();
_sortDescriptions.Add(new SortDescription(path, direction));
return this;
}
public CollectionViewShaper<TSource> GroupBy<TKey>(Expression<Func<TSource, TKey>> keySelector)
{
string path = GetPropertyPath(keySelector.Body);
_groupDescriptions.Add(new PropertyGroupDescription(path));
return this;
}
private static string GetPropertyPath(Expression expression)
{
var names = new Stack<string>();
var expr = expression;
while (expr != null && !(expr is ParameterExpression) && !(expr is ConstantExpression))
{
var memberExpr = expr as MemberExpression;
if (memberExpr == null)
throw new ArgumentException("The selector body must contain only property or field access expressions");
names.Push(memberExpr.Member.Name);
expr = memberExpr.Expression;
}
return String.Join(".", names.ToArray());
}
}
Credit:
http://www.thomaslevesque.com/2011/11/30/wpf-using-linq-to-shape-data-in-a-collectionview/
Based on a visual complexity and number of items there really WILL be a noticable performance difference since the Refresh method recreates the whole view!!!
You need my ObservableComputations library. Using this library you can code like this:
ObservableCollection<Material> MaterialList = MaterialListInternal.Filtering(m =>
String.IsNullOrEmpty(FilterKeywords)
|| DocumentHelper.ContainsCaseInsensitive(
material.Keywords, FilterKeywords, StringComparison.CurrentCultureIgnoreCase));
MaterialList reflects all the changes in the MaterialListInternal collection. Do not forget to add the implementation of the INotifyPropertyChanged interface to Material class, so that MaterialList collection reflects the changes in material.Keywords property.

Resources