I have Form with ComboBox and TextBox. The first contains the column names, and the second contains the text to search for. As a source, ComboBox takes ListTypeSearch from ItemSearch elements. The Search() method is called in the processing of pressing the Search button.
If give the column a name like this, nothing will be found
EF.Functions.Like(item.Value, ...); // Value = "FullName"
If specify a column from the model, the search works
EF.Functions.Like(w.FullName, ...);
Is it possible to replace the column that should be searched within the same Search() method?
ListTypeSearch.Add(new ItemSearch { Value = "FullName", Display = "some text" });
ListTypeSearch.Add(new ItemSearch { Value = "PassportSeries", Display = "some text" });
ListTypeSearch.Add(new ItemSearch { Value = "PassportNumber", Display = "some text" });
public class ItemSearch
{
public string Value { get; set; }
public string Display { get; set; }
}
internal List<WorkerTableRow> Search(ItemSearch item, string text)
{
try
{
Found = new List<WorkerTableRow>();
using (ModelContext model = new ModelContext())
{
Found = (from w in model.Workers
where EF.Functions.Like(w.FullName, // this code
String.Format("%{0}%", text))
select new WorkerTableRow
{
...
})
.ToList();
}
}
catch (Exception ex) { ... }
return Found;
}
Update
Now I did like this. It's works. Can this be simplified?
where EF.Functions.Like(w.GetProperty(item.Value),
String.Format("%{0}%", text))
public partial class Workers
{
...
public string FullName { get; set; }
public string PassportSeries { get; set; }
public string PassportNumber { get; set; }
public string GetProperty(string name)
{
switch (name)
{
case "FullName":
return FullName;
case "PassportSeries":
return PassportSeries;
case "PassportNumber":
return PassportNumber;
default:
return string.Empty;
}
}
}
According other answer. If uses Like(w.GetProperty(item.Value), ...), the request is executed on the client, not on the server. To send the entire request to the server, you can do the following:
List<WorkerTableRow> Search(ItemSearch item, string text)
{
string pattern = string.Format("%{0}%", text);
using (var model = new ModelContext())
{
IQueryable<Worker> query = model.Workers;
if (item.Value == "FullName")
query = query.Where(w => EF.Functions.Like(w.FullName, pattern));
if (item.Value == "PassportSeries")
query = query.Where(w => EF.Functions.Like(w.PassportSeries, pattern));
if (item.Value == "PassportNumber")
query = query.Where(w => EF.Functions.Like(w.PassportNumber, pattern));
return query.Select(w => new WorkerTableRow { ... }).ToList();
}
}
Related
I have the following classes,
User:
public class User:Domain
{
[Sortable]
public string FirstName { get; set; }
[Sortable]
public string LastName { get; set; }
[NestedSortable]
public Profile Profile { get; set; }
}
Profile:
public class Profile : Domain
{
[Sortable]
public string BrandName { get; set; }
[NestedSortable]
public Client Client { get; set; }
[NestedSortable]
public ProfileContact ProfileContact { get; set; }
}
Client:
public class Client : Domain
{
[Sortable]
public string Name { get; set; }
}
Profile Contact:
public class ProfileContact : Domain
{
[Sortable]
public string Email { get; set; }
}
I'm writing a recursive function to get all the properties decorated with [Sortable] attribute. This works well when I have a single [NestedSortableProperty] but fails when I have more than one.
Here is my recursive function:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
List<SortTerm> sortTerms,
string parentsName = null,
bool hasNavigation = false)
{
if (sortTerms is null)
{
sortTerms = new List<SortTerm>();
}
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
if (string.IsNullOrWhiteSpace(parentsName))
{
parentsName = parentType.Name;
}
else
{
parentsName += $".{parentType.Name}";
}
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
}
}
return sortTerms;
}
this happens because of the return statement inside the foreach loop. How to rewrite this? with this example I need to get a list of FirstName,LastName,BrandName,Name and Email. But I'm getting only the first four properties except Email.
Now that the above issue is resolved by removing the return statement as posted in my own answer below and also following #Dialecticus comments to use yield return. so I strike and updated the question.
Now I'm running into another issue. The parent class name is wrongly assigned if a class has multiple [NestedSortable] properties.
This method is called first time with User class like var declaredTerms = GetTermsFromModel(typeof(User), null);
Example,
After the first call, the parentsName parameter will be null and [Sortable] properties in User class don't have any effect.
Now for the [NestedSortable] Profile property in User class, the parentsName will be Profile and so the [Sortable] properties in Profile class will have Name as Profile.BrandName and so on.
Name property in final list to be as follows,
Expected Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.ProfileContact.Email
But Actual Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.Client.ProfileContact.Email
Please assist on how to fix this.
Thanks,
Abdul
after some debugging, I removed the return statement inside foreach loop and that fixed the first issue.
changed from,
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
Then as per #Dialecticus comments removed passing sortTerms as input parameter and removed the parameter inside the code and changed sortTerms.AddRange(...) to yield return.
changed from,
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
to,
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
also for complex properties, changed from,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
var complexProperties = GetTermsFromModel(parentType, parentsName, true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
And for the final issue I'm facing with the name, adding the below code after the inner foreach loop fixed it,
parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
So here is the complete updated working code:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
string parentsName = null,
bool hasNavigation = false)
{
var properties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any());
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
//if (string.IsNullOrWhiteSpace(parentsName))
//{
// parentsName = parentType.Name;
//}
//else
//{
// parentsName += $".{parentType.Name}";
//}
var complexProperties = GetTermsFromModel(parentType, string.IsNullOrWhiteSpace(parentsName) ? parentType.Name : $"{parentsName}.{parentType.Name}", true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
//parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
}
}
}
{High level}, I am trying to post back an ASP.NET MVC view which has multiple 'user inputs' of "search criteria" and use this varying "search criteria" as parameters in the Controller action method which constructs a viewmodel and gets the results from a method of the viewmodel by ultimately passing the 'user inputs' as parameters to that method's {Low level} .FindAll(). Need the ability for user to use one or many or all inputs as their search criteria. For example: User wants to search for Titles which have "John" as [CustFName]. Another example: User wants to search for Titles which have both "John" as [CustFName] and "Holmes" as [CustLName].
public class SearchTitleViewModel
{
private readonly ApplicationDbContext _db = new ApplicationDbContext();
public SearchTitleViewModel()
{
var tits = _db.Titles.ToList();
}
public Title Title { get; set; }
public List<Title> Titles { get; set; }
public SearchTitleViewModel GetTitlesWithSearchCriteria(string office, bool? mv = null, int id = -1, string custFName = null, string custLName = null, string vin = null)
{
SearchTitleViewModel searchtitlevm = new SearchTitleViewModel();
if (id > -1)
{
searchtitlevm.Title = _db.Titles.Find(id);
searchtitlevm.Titles.Add(searchtitlevm.Title);
return searchtitlevm;
}
searchtitlevm.Titles.AddRange(
Titles.FindAll(
tit => tit.CustFName == custFName && // want to make "tit.CustFName == custFName" a variable
tit.CustLName == custLName &&// want to make "tit.CustFName == custFName" a variable
tit.InitialLocation == office
)
);
return searchtitlevm;
}
}
Ultimately I would like to pass in multiple lambda expressions to .FindAll() as variables that are declared at runtime like FindAll(Title tit, param1 custFNameExpression, param2 custLNameExpression, ...) but have not been able to find any examples to do something like this. I did find examples of creating a delegate[] but still does not solve the issue of adding an expression or not based on if the user supplied that one or not from the view.
Am I going about this all wrong? I mean is there a simpler way to do this?
I gave up trying to make .FindAll() work for what I needed. I used .SqlQuery() instead:
public class SearchTitleViewModel
{
private readonly ApplicationDbContext _db = new ApplicationDbContext();
public SearchTitleViewModel()
{
Title = new Title()
{
NtyId = 1//for testing REMOVE
};
Titles = new List<Title>();
}
public Title Title { get; set; }
public List<Title> Titles { get; set; }
public SearchTitleViewModel GetTitlesWithSearchCriteria(SearchTitleViewModel vm)
{
var searchtitlevm = new SearchTitleViewModel();
if (vm.Title.TitleNum != null)
{
searchtitlevm.Title = _db.Titles.Find(vm.Title.TitleNum);
if (searchtitlevm.Title != null && searchtitlevm.Title.NtyId != vm.Title.NtyId)
{
return searchtitlevm;
}
searchtitlevm.Titles.Add(searchtitlevm.Title);
return searchtitlevm;
}
var titssql = "SELECT * FROM Titles WHERE";
if (vm.Title.Vin != null) { titssql += $" Vin = \'{vm.Title.Vin}\' AND "; }
if (vm.Title.CustLName != null) { titssql += $" CustLName = \'{vm.Title.CustLName}\' AND "; }
if (vm.Title.CustFName != null) { titssql += $" CustFName = \'{vm.Title.CustFName}\' AND "; }
titssql += $" NtyId = \'{vm.Title.NtytyId}\'";
searchtitlevm.Titles = _db.Titles.SqlQuery(titssql).ToList();
return searchtitlevm;
}
}
I am building a parser for a custom pipe delimited file format and I am finding my code to be very bulky, could someone suggest better methods of parsing this data?
The file's data is broken down by a line delimited by a pipe (|), each line starts with a record type, followed by an ID, followed by different number of columns after.
Ex:
CDI|11111|OTHERDATA|somemore|other
CEX001|123131|DATA|data
CCC|123131|DATA|data1|data2|data3|data4|data5|data6
. I am splitting by pipe, then grabbing the first two columns, and then using a switch checking the first line and calling a function that will parse the remaining into an object purpose built for that record type. I would really like a more elegant method.
public Dictionary<string, DataRecord> Parse()
{
var data = new Dictionary<string, DataRecord>();
var rawDataDict = new Dictionary<string, List<List<string>>>();
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var Id = split[1];
if (!rawDataDict.ContainsKey(Id))
{
rawDataDict.Add(Id, new List<List<string>> {split.ToList()});
}
else
{
rawDataDict[Id].Add(split.ToList());
}
}
rawDataDict.ToList().ForEach(pair =>
{
var key = pair.Key.ToString();
var values = pair.Value;
foreach (var value in values)
{
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value);
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
}
});
return data;
}
Try out FileHelper, here is your exact example - http://www.filehelpers.net/example/QuickStart/ReadFileDelimited/
Given you're data of
CDI|11111|OTHERDATA|Datas
CEX001|123131|DATA
CCC|123131
You could create a class to model this to allow FileHelpers to parse the delimited file:
[DelimitedRecord("|")]
public class Record
{
public string Type { get; set; }
public string[] Fields { get; set; }
}
Then we could allow FileHelpers to parse in to this object type:
var engine = new FileHelperEngine<Record>();
var records = engine.ReadFile("Input.txt");
After we've got all the records loaded in to Record objects we can use a bit of linq to pull them in to their given types
var cdis = records.Where(x => x.Type == "CDI")
.Select(x => new Cdi(x.Fields[0], x.Fields[1], x.Fields[2])
.ToArray();
var cexs = records.Where(x => x.Type == "CEX001")
.Select(x => new Cex(x.Fields[0], x.Fields[1)
.ToArray();
var cccs = records.Where(x => x.Type == "CCC")
.Select(x => new Ccc(x.Fields[0])
.ToArray();
You could also simplify the above using something like AutoMapper - http://automapper.org/
Alternatively you could use ConditionalRecord attributes which will only parse certain lines if they match a given criteria. This will however be slower the more record types you have but you're code will be cleaner and FileHelpers will be doing most of the heavy lifting:
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CDI")]
public class Cdi
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CEX001")]
public class Cex001
{
public string Type { get; set; }
public int Number { get; set; }
public string Data1 { get; set; }
}
[DelimitedRecord("|")]
[ConditionalRecord(RecordCondition.IncludeIfMatchRegex, "^CCC")]
public class Ccc
{
public string Type { get; set; }
public int Number { get; set; }
}
var input =
#"CDI|11111|Data1|Data2|Data3
CEX001|123131|Data1
CCC|123131";
var CdiEngine = new FileHelperEngine<Cdi>();
var cdis = CdiEngine.ReadString(input);
var cexEngine = new FileHelperEngine<Cex001>();
var cexs = cexEngine.ReadString(input);
var cccEngine = new FileHelperEngine<Ccc>();
var cccs = cccEngine.ReadString(input);
Your first loop isn't really doing anything other than organizing your data differently. You should be able to eliminate it and use the data as it is from the file. Something like this should give you what you want:
foreach (var line in File.ReadLines(_path))
{
var split = line.Split('|');
var key = split[1];
var value = split;
var recordType = value[0];
switch (recordType)
{
case "CDI":
var cdiRecord = ParseCdi(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key, CdiRecords = new List<CdiRecord>() { cdiRecord }
});
}
else
{
data[key].CdiRecords.Add(cdiRecord);
}
break;
case "CEX015":
var cexRecord = ParseCex(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CexRecords = new List<Cex015Record>() { cexRecord }
});
}
else
{
data[key].CexRecords.Add(cexRecord);
}
break;
case "CPH":
CphRecord cphRecord = ParseCph(value.ToList());
if (!data.ContainsKey(key))
{
data.Add(key, new DataRecord
{
Id = key,
CphRecords = new List<CphRecord>() { cphRecord }
});
}
else
{
data[key].CphRecords.Add(cphRecord);
}
break;
}
};
Caveat: This is just put together here and hasn't been properly checked for syntax.
I'm work with C# and linq, and am trying to return some results from a table in a string, or array.
I am able to create a query, look up the row, but when I try to return column values, I get empty array of my table column names as my string.
Here is my result:
SELECT [t0].[UserID], [t0].[First], [t0].[Last], [t0].[Username], [t0].[Password], [t0].[Employee], [t0].[City], [t0].[Branch], [t0].[UserPoints], [t0].[CurrentPoints], [t0].[LastOnline], [t0].[Status] FROM [dbo].[mrobUsers] AS [t0] WHERE [t0].[Username] = #p0
Here is my Linq query:
public string GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
var getInfo = (from row
in db.mrobUsers
where row.Username == username
select row).ToString();
return getInfo;
// I've debugged up to here, and my user name is passed into this
}
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
My ideal result would be:
1,Mark,Rob,mrob88, password....etc
You could try this one:
// You should change the return type of your method, if you want to return an array of
// strings.
public string[] GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
// Get the user's info.
var userInfo = (from row in db.mrobUsers
where row.Username == username
select row).SingleOrDefault();
// If the user's info found return the corresponding values.
if(userInfo!=null)
{
var t = typeof(userInfo);
List<string> values = new List<string>();
foreach(var prop in t.GetProperties())
{
values.Add(prop.GetValue(userInfo, null);
}
return values.ToArray();
}
else // the user's info not found and return an empty array.
{
return new string[] { };
}
// I've debugged up to here, and my user name is passed into this
}
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
}
However, I suggest you not follow this approach. I think it would be better, if you declare a class with properties the values that you want to retrieve, like below:
public class UserInfo
{
public int UserId { get; set; }
public string First { get; set; }
public string Last { get; set; }
// the same way you will declare the rest of your properties.
}
Then you could change your method to the following one:
public UserInfo GetUserInfo(string username)
{
try
{
using (UserDataDataContext db = new UserDataDataContext())
{
// Get the user's info.
var userInfo = (from row in db.mrobUsers
where row.Username == username
select new UserInfo
{
UserId = row.UserId,
First = row.First
Last = row.Last
}).SingleOrDefault();
return userInfo;
}
catch (Exception e)
{
return MySerializer.Serialize(false);
}
}
}
I have class where i have the collection of FreeDressingItems, FreeToppingItems, FreeInstructionItems
that is like this each of which fill selectedCustomization i have another property in this class public string Items { get { return GetAllItems(); } }
that i want to fill so that it keeps the all catetoryname for the same category type so that i can bind it to grid easily and display all its value in comma separated form.
i have following code could somebody help me how can i acineve this.
public class selectedCustomization
{
public CategoryType TypeName { get; set; }
public string CategoryName { get; set; }
public string ItemName { get; set; }
public int SourceID { get; set; }
public string Items { get { return GetAllItems(); } }
private string GetAllItems()
{
switch (TypeName)
{
case CategoryType.Dressing:
{
cFreeCustomization cfreeCust = new cFreeCustomization();
break;
}
case CategoryType.Topping:
break;
case CategoryType.SpecialInstruction:
break;
}
}
}
this is another class cFreeCustomization
public List<selectedCustomization> SelectedItems
{
get
{
libDBDataContext cn = new libDBDataContext();
List<selectedCustomization> lst = new List<selectedCustomization>();
lst.AddRange(
(from xx in this.FreeDressingItems
select new selectedCustomization() { TypeName = CategoryType.Dressing, CategoryName = xx.DressingInfo.CatName, ItemName = xx.DressingInfo.Description }
).ToList()
);
lst.AddRange(
(from xx in this.FreeToppingItems
select new selectedCustomization() { TypeName = CategoryType.Topping, CategoryName = xx.ToppingInfo.CatName, ItemName = xx.ToppingInfo.Description }
).ToList()
);
lst.AddRange(
(from xx in this.FreeInstructionItems
select new selectedCustomization() { TypeName = CategoryType.SpecialInstruction, CategoryName = xx.InstructionInfo.CatName, ItemName = xx.InstructionInfo.Description }
).ToList()
);
return lst;
}
}
How can i make tiems of selectedCustomization in comma separated form?
I believe the method GetAllItems should be like below:
private string GetAllItems()
{
cFreeCustomization cfreeCust = new cFreeCustomization();
var ls = cfreeCust.SelectedItems.FindAll(I => I.TypeName == this.TypeName);
return string.Join(",", ls.Select(I => I.CategoryName).ToArray());
}
This will fix your issue.