We need to log specific action in MVC 3 project. A database table will store logs something like:
"User [SessionUserHere...] changed [Name][Lastname][OtherAttributesHere...] values of [ChangedEmployeeHere...]"
I need to learn which attributes of a model changed and which ones keep their original values.
Is there any way to track which attributes of a Model changed?
In MVC3 doing Audit trail a database trigger is proposed; but we use Sql Server Compact for this project.
Thanks...
Have you had look at the INotifyPropertyChanged interface?
http://msdn.microsoft.com/en-us/library/ms743695.aspx
You could do the audit in code rather than the database. In the HttpPost handler, get the original value and compare the objects using an auditing function. I have a home brew implementation, but this does a similar thing Compare .NET Objects.
It means you can do the following:
var original = GetDatabaseRecord(xx);
var newRec = GetFormSubmission(); // However you do this
var auditor = new CompareObjects();
if ( auditor.Compare(original, newRec) )
{
foreach ( var diff in auditor.Differences )
{
// work through the deltas
}
}
else
{
// Nothing changed!
}
My own version returns a structure of:
Name (such as "Issue.Priority")
Change (ChangedValue, OnlyLeft, OnlyRight)
Old Value
New Value
The link provided may suffice for you or act as a starting point.
I created a library to do just this and provide some additional metadata. It relies on MVC ModelMetadata and DataAnnotations to provide a "readable version" of the diff for non technical users.
https://github.com/paultyng/ObjectDiff
Given objects like (no metadata obviously):
var before = new
{
Property1 = "",
MultilineText = "abc\ndef\nghi",
ChildObject = new { ChildProperty = 7 },
List = new string[] { "a", "b" }
};
var after = new
{
Property1 = (string)null,
MultilineText = "123\n456",
NotPreviouslyExisting = "abc",
ChildObject = new { ChildProperty = 6 },
List = new string[] { "b", "c" }
};
It would output something like:
ChildObject - ChildProperty: '6', was '7'
List - [2, added]: 'c', was not present
List - [removed]: No value present, was 'a'
MultilineText:
-----
123
456
-----
was
-----
abc
def
ghi
-----
NotPreviouslyExisting: 'abc', was not present
Related
I have a List that contains 2 properties per object. The properties are as follows:
string Project;
double Value;
So in any given case we might have a List of 5 objects, where 3 of them have a Project property called "Test" and the other 2 objects have a Project Property called "Others", but none of the 5 objects have the same "Value".
{
Project = "Test" Value = 1,
Project = "Test" Value = 5,
Project = "Test" Value = 25,
Project = "Others" Value = 89,
Project = "Others" Value = 151
}
Okay, I get a lot of data from a Database (I "Query" it out into a List of objects), then I take the specific properties I need from that List and add to my own List as follows.
public class Data
{
public string Project {get; set;}
public double Value {get; set;}
}
public List<Data> dataList = new List<Data>();
foreach(var item in DatabaseList)
{
Data newData = new Data(
data.Project = item.Project;
data.Value = item.Project;
dataList.Add(newData);
}
This gives me my list of data that I somehow need to combine based on the property in "Project"
But I have a hard time figuring out how to seperate them from one another, my first thought was to find "Unique" "Projects" and adding that to a new List called "counter", to then loop through that list based of the "Project" property, so something like this:
List<Data> counter = dataList.GroupBy(x => x.Project).Select(First()).ToList();
foreach(var item in counter)
{
Data finalItem = new Data();
foreach (var item2 in dataList)
{
if(item.Project == item2.Project)
{
finalItem.Project = item2.Project;
finalItem.Value += item2.Value;
finalList.Add(finalItem);
}
}
}
So I already know that the above is so messy its crazy, and its also not going to work, but this was the angle I was trying to take, I was also thinking whether I could maybe make use of Dictionary, but I feel like there is probably a super simple solution to something like this.
I think your initial thoughts regarding making use of a dictionary are good. Your use of .GroupBy() is a first step to create that dictionary, where the project name is the dictionary Key and the sum of values for that project is the dictionary Value.
You already seem to be familiar with the System.Linq namespace. The extension method .ToDictionary() exists in the same namespace, and can be used to define the Key and Value selector for each KeyValuePair (KVP) in the dictionary as follows:
.ToDictionary(
<selector for Key>,
<selector for Value>
);
The dictionary may be created by utilizing .ToDictionary() directly after .GroupBy(), as follows:
Dictionary<string, double> dataDictionary = dataList
.GroupBy(item => item.Project)
.ToDictionary(
itemsByProject => itemsByProject.Key,
itemsByProject => itemsByProject.Sum(item => item.Value));
Example fiddle here.
You can use the following code to compute the total Value for objects with Project="Test" :
double TestsValue = my_list.Where(o=>o.Project=="Test").Sum(t=>t.Value);
and do the same for "Others".
Assuming you're happy to return an IEnumerable of Data, you can do this:
var projects = dataList.GroupBy(p => p.Project)
.Select(grp =>
new Data
{
Project = grp.First().Project,
Value = grp.Sum(pr => pr.Value)
});
I can search member by EntityMembersGetCriteria where SearchTerm containing expressions for custom attributes for exampel SAP Code = 'abc'.
var criteria = new EntityMembersGetCriteria
{
ModelId = new Identifier { Name = Model },
EntityId = new Identifier { Name = Entity },
VersionId = new Identifier { Name = Version },
SearchTerm = searchTerm,
MemberType = MemberType.Leaf,
MemberReturnOption = MemberReturnOption.DataAndCounts
};
However it does work with Code='xyz'.
Any idea pls?
SearchTerm sets WHERE clause search criteria to filter records. That means it is needed to put exactly the same names as it is stored in the MDS DB.
The point is that the user defined attributes (UDA) have special names in MDS DB.
Every UDA attributes is stored in the following format:
uda_{Entity.InternalId}_{Attribute.InternalId}, for example uda_2012_45231. In case of Entity.InternalId = 2021 and Attribute.InternalId = 45231.
So you have write into SearchTerm:
"uda_2012_45231 = 'abc'"
P.S.: You can find the values of the Entity and Attribute InternalIds within MetadataGet method.
UPDATE: It seems that UDA attributes do not work within SearchTerm at all. "Name", "Code", "EnterDTM" (CreatedDateTime) and "LastChgDTM" (UpdatedDateTime) are working, but the UDA are not.
In my ASP.NET MVC Application, I have many actions that return JSONs.
In these JSONs, some sub-structures are repeated.
For example
ajax: "/Bands/Members" is supposed to return the members of a band, which are all musicians
ajax: "/Musicians/All" is supposed to return all the musicians on the system
ajax: "/Musicians/Search" is supposed to return all the musicians that match something
etc...
Here I show (1):
public JsonResult Members(long musicBandId)
{
MusicBand b = db.MusicBands.SingleOrDefault(b => b.MusicBandId == musicBandId);
if (b == null)
return null;
return Json(new
{
error = false,
message = "",
persons = from m in b.Members select new
{
musicianId = p.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}, JsonRequestBehavior.AllowGet);
}
And here I show (2):
public JsonResult All(int page, int pageSize)
{
var musicians = db.Musicians;
var pageOfMusicians = musicians.Skip((page-1) * pageSize).Take(pageSize);
return Json(new
{
error = false,
message = "",
musiciansCount = musicians.Count(),
page = page,
pageSize = pageSize
musicians = from m in pageOfMusicians select new
{
musicianId = m.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}, JsonRequestBehavior.AllowGet);
}
This has several problems:
If I want to change the JSON format, I have to change it in every single action!
example: If I want to change "name" to "fullname", I have to change it in Members() and All()
Lot of "copy pasting": If I'm creating a new action that somewhere in the structure returns a musician, I need to copy and paste that piece of code that represents the musician
{
musicianId = p.MusicianId,
name = p.Name,
instrument = new
{
instrumentId = instrument.InstrumentId,
model = instrument.Model
}
}
Code is too large
What solution exists to this problem?
If you propose a framework, please show me how would the previous queries look with that framework
Notice I'm always using Linq-to-entities in my examples, and I would like to keep it like that because of performance issues. I know that with Linq-to-objects I could to something like:
from m in pageOfMusicians.ToList() select m.GetDTO()
Being GetDTO() some method that can be run with Linq-to-Objects.
But then, again, I want to stick to Linq-to-Entities
Alternative 1
If you don't worry about using dynamics mixed with regular typed C# code you could make a utility method like...
public static dynamic PrepareForMusiciansView(IQuerable<Musician> musicians)
{
return musicians.Select(m => new
{
musicianId = m.MusicianId,
name = m.Name,
instrument = new
{
instrumentId = m.instrument.InstrumentId,
model = m.instrument.Model
}
}
}
...and then...
return Json(new
{
error = false,
message = "",
musiciansCount = musicians.Count(),
page = page,
pageSize = pageSize
musicians = Util.PrepareForMusiciansView(pageOfMusicians)
}, JsonRequestBehavior.AllowGet);
The method name should clearly reflect its purpose in terms of your application. Maybe you want to focus more on the serialization function and use a name like PrepareForJson. Also, it should be consistent with your coding standards. I would avoid it if nowhere else dynamics is used.
Alternative 2
Use AutoMapper (available via NuGet).
With AutoMapper you'd typically have DTO classes for Musician and Instrument, having the properties you want to expose in the view. If these properties have the same names as those in the source classes, AutoMapper will match them by name convention.
Using AutoMapper always involves defining the mappings and executing mappings. Defining the mappings should be done once at application startup. It looks like...
Mapper.CreateMap<Musician, MusicianDto>();
Mapper.CreateMap<Instrument, InstrumentDto>();
There are different ways to execute the mappings, but when working with IQueryable the preferred way is
musicians = pageOfMusicians.Project().To<MusicianDto>()
This projects IQueryable<Musician> to IQueryable<MusicianDto>, including the nested Instrument (if the DTO has a property of that name).
These are two viable alternatives I can think of to reduce the awkward new {} statements to reusable one-liners. It really depends on your application and coding style which alternative you prefer.
I´m trying to make a query in ElasticSearch with the NEST c# client a query without accent, my data has portuguese latin word with accent. See the code bellow:
var result = client.Search<Book>(s => s
.From(0)
.Size(20)
.Fields(f => f.Title)
.FacetTerm(f => f.OnField(of => of.Genre))
.Query(q => q.QueryString(qs => qs.Query("sao")))
);
This search did not find anything. My data on this index contains many titles like: "São Cristóvan", "São Gonçalo".
var settings = new IndexSettings();
settings.NumberOfReplicas = 1;
settings.NumberOfShards = 5;
settings.Analysis.Analyzers.Add("snowball", new Nest.SnowballAnalyzer { Language = "Portuguese" });
var idx5 = client.CreateIndex("idx5", settings);
How I can make query "sao" and find "são" using ElasticSearch?
I think have to create index with right properties, but I already tried many settings like.
or in Raw Mode:
{
"idx" : {
"settings" : {
"index.analysis.filter.jus_stemmer.name" : "brazilian",
"index.analysis.filter.jus_stop._lang_" : "brazilian"
}
}
}
How can I make the search and ignore accents?
Thanks Friends,
See the solution:
Connect on elasticsearch search with putty execute:
curl -XPOST 'localhost:9200/idx30/_close'
curl -XPUT 'localhost:9200/idx30/_settings' -d '{
"index.analysis.analyzer.default.filter.0": "standard",
"index.analysis.analyzer.default.tokenizer": "standard",
"index.analysis.analyzer.default.filter.1": "lowercase",
"index.analysis.analyzer.default.filter.2": "stop",
"index.analysis.analyzer.default.filter.3": "asciifolding",
"index.number_of_replicas": "1"
}'
curl -XPOST 'localhost:9200/idx30/_open'
Replace "idx30" with name of your index
Done!
I stumbled upon this thread since I got the same problem.
Here's the NEST code to create an index with an AsciiFolding Analyzer:
// Create the Client
string indexName = "testindex";
var uri = new Uri("http://localhost:9200");
var settings = new ConnectionSettings(uri).SetDefaultIndex(indexName);
var client = new ElasticClient(settings);
// Create new Index Settings
IndexSettings set = new IndexSettings();
// Create a Custom Analyzer ...
var an = new CustomAnalyzer();
// ... based on the standard Tokenizer
an.Tokenizer = "standard";
// ... with Filters from the StandardAnalyzer
an.Filter = new List<string>();
an.Filter.Add("standard");
an.Filter.Add("lowercase");
an.Filter.Add("stop");
// ... just adding the additional AsciiFoldingFilter at the end
an.Filter.Add("asciifolding");
// Add the Analyzer with a name
set.Analysis.Analyzers.Add("nospecialchars", an);
// Create the Index
client.CreateIndex(indexName, set);
Now you can Map your Entity to this index (it's important to do this after you created the Index)
client.MapFromAttributes<TestEntity>();
And here's how such an entity could look like:
[ElasticType(Name = "TestEntity", DisableAllField = true)]
public class TestEntity
{
public TestEntity(int id, string desc)
{
ID = id;
Description = desc;
}
public int ID { get; set; }
[ElasticProperty(Analyzer = "nospecialchars")]
public string Description { get; set; }
}
There you go, the Description-Field is now inserted into the index without accents.
You can test this if you check the Mapping of your index:
http://localhost:9200/testindex/_mapping
Which then should look something like:
{
testindex: {
TestEntity: {
_all: {
enabled: false
},
properties: {
description: {
type: "string",
analyzer: "nospecialchars"
},
iD: {
type: "integer"
}
}
}
}
}
Hope this will help someone.
You'll want to incorporate an ACSII Folding filter into your analyzer to accomplish this. That will mean constructing the snowballanalyzer form tokenizers and filters (unless nest allows you to add filters to non-custom analyzers. ElasticSearch doesn't, though, as far as I know).
A SnowballAnalyzer incorporates:
StandardTokenizer
StandardFilter
(Add the ASCIIFolding Filter here)
LowercaseFilter
StopFilter (with the appropriate stopword set)
SnowballFilter (with the appropriate language)
(Or maybe here)
I would probably try to add the ASCIIFoldingFilter just before LowercaseFilter, although it might be better to add it as the very las step (after SnowballFilter). Try it both ways, see which works better. I don't know enough about either the Protuguese stemmer to say which would be best for sure.
I have list that have values like"
[0] = "{ id = ES10209005, views = 501 }"
[1] = "{ id = HYT0209005, views = 5678}"
[3] = "{ id = POI0209005, views = 4568}"
I would like to pass the values(id,views) to a method using a for each loop.
method(id,views)
Something like:
foreach (string v in updatereponse)
{
method()
}
How do I isolate each value(id,views) from each row in the list then pass it to the method?
The list contains just a bunch of strings, anything based on this to fix the problem would be just a workaround (e.g. string parsing). You should really switch to a strongly typed model, e.g. define a class ViewCount:
public class ViewCount
{
public string Id {get;set;}
public int Views {get;set;}
}
You can then use a List<ViewCount> populate the list:
List<ViewCount> viewcounts = new List<ViewCount>();
viewCounts.Add(new ViewCount() { Id = "ES10209005", Views = 501 });
Since each ViewCount instance has Id and Views properties you can now do the proper thing:
foreach (var item in updatereponse)
{
method(item.Id, item.Views);
}
If you are saving this data in a file, an alternative would be to use XML instead of custom strings, then you could use Linq to XML to populate a List<ViewCount>, e.g. using a simple XML like this:
<ViewCounts>
<ViewCount id="ES10209005" views="501" />
</ViewCounts>
You can then load your list:
XElement viewXml = XElement.Load("test.xml");
List<ViewCount> viewCounts = viewXml.Descendants("ViewCount")
.Select(x => new ViewCount()
{
Id = (string)x.Attribute("id"),
Views = (int)x.Attribute("views")
}).ToList();
foreach (string v in updateresponse)
{
var values = v.Split(",");
var id = values[0].Replace("{ id = ", "").Trim();
var view = values[1].Replace("views = ", "").("}","").Trim();
method(id, value);
}
Here's another way...you may want to add error checking:
String Data = "{ id = ES10209005, views = 501 }";
String[] Segments = Data.Split(new char[] { ' ', ',' });
string ID = Segments[3];
int views = int.Parse(Segments[7]);
Assuming the structure of your String is like you showed us always, this can work for you.
// First split id and views part.
String[] firstSplit = v.Split(',');
// Get the respected value for each part.
String id = firstSplit[0].Split('=')[1].Trim();
String views = firstSplit[1].Split('=')[1].Trim().Replace("}","");
You can use String methods to retrieve the items (use Split and SubString for example) or you can use a regular expression.
E.g.
var list = UpdateResponse[0].Split("=,} ") ;
will result in a list split by all these characters (including space).
Then check the correct indices to use (use a debugger for that). Then you get something like:
var id = list[5];
var views = list[8];
(note: check the indices 5 or 8, they are just a guess).