আমি কীভাবে লিনাক অর্ডার দ্বারা যুক্তিটি গতিশীলভাবে নির্দিষ্ট করব?


95

আমি যুক্তিটি কীভাবে নির্দিষ্ট করব orderby প্যারামিটার হিসাবে গ্রহণ করা মানটি ব্যবহার করে করব?

প্রাক্তন:

List<Student> existingStudends = new List<Student>{ new Student {...}, new Student {...}}

বর্তমানে বাস্তবায়ন:

List<Student> orderbyAddress = existingStudends.OrderBy(c => c.Address).ToList();

পরিবর্তে c.Address , আমি কীভাবে এটি পরামিতি হিসাবে নিতে পারি?

উদাহরণ

 string param = "City";
 List<Student> orderbyAddress = existingStudends.OrderByDescending(c => param).ToList();

4
: আপনি ডায়নামিক Linq উপস্থিত হতে আগ্রহী weblogs.asp.net/scottgu/archive/2008/01/07/...
BrokenGlass

@ নেভ_ রাহাদ: প্রশ্নটি কিছুটা স্পষ্ট করার চেষ্টা করেছেন। এছাড়াও, OrderByএকটি Linq বৈশিষ্ট্য, এবং চালু থাকে IEnumerableকোনো বৈশিষ্ট্যের নির্দিষ্ট নয়, List। সম্পাদনাটি পিছনে ফিরতে বা এটিকে আরও পরিবর্তন করতে নির্দ্বিধায় :)
মের্লিন মরগান-গ্রাহাম

উত্তর:


129

প্রতিবিম্ব ব্যবহার করে এখানে একটি সম্ভাবনা রয়েছে ...

var param = "Address";    
var propertyInfo = typeof(Student).GetProperty(param);    
var orderByAddress = items.OrderBy(x => propertyInfo.GetValue(x, null));

4
কিন্তু এটি কি সত্য যখন সত্ত্বা ফ্রেমওয়ার্ক (এসকিএল সার্ভার, বা অন্য) এর মতো সরবরাহকারী দ্বারা ব্যাখ্যা করা লিনাক এক্সপ্রেশন আসে ??
a.boussema

4
@vijay - ThenByপদ্ধতিটি ব্যবহার করুন ।
কোডকনশন

9
যখন আমি এটি চেষ্টা করি ত্রুটিটি পাই: সত্তা থেকে লাইনকিউ 'সিস্টেম.অজেক্ট গেটভ্যালু (সিস্টেম.অজেক্ট, সিস্টেম.অজেক্ট [])' পদ্ধতিটি স্বীকৃতি দেয় না এবং এই পদ্ধতির কোনও স্টোর এক্সপ্রেশন হিসাবে অনুবাদ করা যায় না। এই উত্তরটি কি কেবল লিনাক টু এসকিউএল-এ প্রযোজ্য?
ফিল্ড করা

4
.AsEnumerable () এর সাথে কোনও ত্রুটি নেই: var আদেশByAddress = items.AsEnumerable ()। অর্ডারবি (x => সম্পত্তিInfo.GetValue (x, নাল));
সিজার

4
আমি কীভাবে
এসএসসি

128

আপনি নিম্নরূপে অভিব্যক্তি গাছটি তৈরি করতে কিছুটা প্রতিবিম্ব ব্যবহার করতে পারেন (এটি একটি এক্সটেনশন পদ্ধতি):

public static IQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty,
                          bool desc) 
{
     string command = desc ? "OrderByDescending" : "OrderBy";
     var type = typeof(TEntity);
     var property = type.GetProperty(orderByProperty);
     var parameter = Expression.Parameter(type, "p");
     var propertyAccess = Expression.MakeMemberAccess(parameter, property);
     var orderByExpression = Expression.Lambda(propertyAccess, parameter);
     var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
                                   source.Expression, Expression.Quote(orderByExpression));
     return source.Provider.CreateQuery<TEntity>(resultExpression);
}

orderByPropertyআপনি যে সম্পত্তির নামটি অর্ডার করতে চান এবং এটি যদি প্যারামিটার হিসাবে সত্য descহয় তবে অবতরণ ক্রমে সাজানো হবে; অন্যথায়, আরোহী ক্রম বাছাই করা হবে।

এখন আপনি করতে সক্ষম হতে হবে existingStudents.OrderBy("City",true);বাexistingStudents.OrderBy("City",false);


10
এই উত্তরটি দুর্দান্ত এবং প্রতিবিম্ব উত্তরের চেয়ে অনেক ভাল। এটি আসলে সত্তা কাঠামোর মতো অন্যান্য সরবরাহকারীদের সাথে কাজ করে।
স্যাম

4
আমি পারলে এইবার দশবার ভোট দিতাম !!! আপনি এভাবে এক্সটেনশন পদ্ধতি লিখতে শিখেন কোথায় ?? !!
জ্যাচ

4
বিল্ট-ইন অর্ডারবাইয়ের মতোই কি এই আইওআরআরডিক্যুয়্যারেবল ফিরিয়ে দেওয়া উচিত? এইভাবে, আপনি কল করতে পারেন .তখন এটি দ্বারা।
প্যাট্রিক জাজালাপস্কি

4
এটি EFCore 3.0 ব্যবহার করার সময় আর কাজ করে না বলে মনে হচ্ছে, আমি রানটাইম ত্রুটি পেয়েছি যেখানে এটি ক্যোরির অনুবাদ করতে পারে না।
মাইল্ডান

4
হ্যাঁ, @ মিল্ডান, এটি আমার জন্য 3.0 এবং 3.1 এও ভেঙে যায়। ত্রুটি সহ can "অনুবাদ অনুবাদ" with যদি প্রাসঙ্গিক হয় তবে আমি মাইএসকিউএলের জন্য পোমেলো ব্যবহার করি। সমস্যাটি হ'ল এক্সপ্রেশন। যদি আপনি হাতের কোডটি প্রকাশ করে তবে তা কাজ করে। সুতরাং ল্যাম্বদা.এক্সপ্রেশনের পরিবর্তে (যেমন) কিছু সরবরাহ করুন: ল্যাম্বডএ এক্সপ্রেসনের অর্ডারবাই এক্সপ্স ১ = (এক্সপ্রেশন <ফানক <এজেন্সি সিস্টেমস্টিজ, স্ট্রিং >>) (x => x.Name);
মেনেস

9

@ আইকারাসের উত্তরে প্রসারিত করার জন্য : আপনি যদি আইটিক্যারেবলের পরিবর্তে এক্সটেনশন পদ্ধতির রিটার্ন টাইপকে আইওরিডকুইয়েরিযোগ্য হতে চান তবে আপনি কেবল ফলাফলটি নীচের মত ফেলে দিতে পারেন:

public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string orderByProperty, bool desc)
{
    string command = desc ? "OrderByDescending" : "OrderBy";
    var type = typeof(TEntity);
    var property = type.GetProperty(orderByProperty);
    var parameter = Expression.Parameter(type, "p");
    var propertyAccess = Expression.MakeMemberAccess(parameter, property);
    var orderByExpression = Expression.Lambda(propertyAccess, parameter);
    var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType },
        source.Expression, Expression.Quote(orderByExpression));
    return (IOrderedQueryable<TEntity>)source.Provider.CreateQuery<TEntity>(resultExpression);
}

4
দেখে মনে হচ্ছে অন্যান্য উত্তর সত্তা ফ্রেমওয়ার্কের জন্য উপযুক্ত ছিল না। এটি EF- এর জন্য একটি নিখুঁত সমাধান কারণ লিনাক টু সত্তাটি গেটপোপার্টি, গেটভ্যালু সমর্থন করে না
বিল

4
এই পদ্ধতিটি আমার জন্য 3.0 এবং 3.1 এ ব্যর্থ বলে মনে হচ্ছে (এটি 2.2 তে কাজ করেছে)। আমি MySQL এর জন্য পোমেলো ব্যবহার করি যাতে এটি প্রাসঙ্গিক হতে পারে। চারপাশে একটি কাজ আছে তবে এটি কুৎসিত। আমার মন্তব্য উপরে দেখুন।
মেনেস

এটি EF 3.0 এ আমার জন্য কাজ করেছিল। তবে, আপনার নিম্নলিখিত লাইনটি পরিবর্তন করা উচিত যাতে সামনের প্রান্তটি
কিং আর্থার তৃতীয়

এটি কি এখনও কোর 3.1 এর জন্য অনুকূলিত?
ক্রিস গো

8

1) ইনস্টল করুন সিস্টেম.লিনক.ডায়নামিক

2) নিম্নলিখিত কোড যুক্ত করুন

public static class OrderUtils
{
    public static string ToStringForOrdering<T, TKey>(this Expression<Func<T, TKey>> expression, bool isDesc = false)
    {
        var str = expression.Body.ToString();
        var param = expression.Parameters.First().Name;
        str = str.Replace("Convert(", "(").Replace(param + ".", "");
        return str + (isDesc ? " descending" : "");
    }
}

3) ল্যাম্বদা ফাংশন নির্বাচনের জন্য আপনার সুইচটি লিখুন

public static class SortHelper
{
    public static Expression<Func<UserApp, object>> UserApp(string orderProperty)
    {
        orderProperty = orderProperty?.ToLowerInvariant();
        switch (orderProperty)
        {
            case "firstname":
                return x => x.PersonalInfo.FirstName;
            case "lastname":
                return x => x.PersonalInfo.LastName;
            case "fullname":
                return x => x.PersonalInfo.FirstName + x.PersonalInfo.LastName;
            case "email":
                return x => x.Email;

        }
    }
}

4) আপনার সাহায্যকারীদের ব্যবহার করুন

Dbset.OrderBy(SortHelper.UserApp("firstname").ToStringForOrdering())

5) আপনি এটি প্যাগিং ( পেজডলিস্ট ) দিয়ে ব্যবহার করতে পারেন

public virtual  IPagedList<T> GetPage<TOrder>(Page page, Expression<Func<T, bool>> where, Expression<Func<T, TOrder>> order, bool isDesc = false,
      params Expression<Func<T, object>>[] includes)
    {
        var orderedQueryable = Dbset.OrderBy(order.ToStringForOrdering(isDesc));
        var query = orderedQueryable.Where(where).GetPage(page);
        query = AppendIncludes(query, includes);

        var results = query.ToList();
        var total =  Dbset.Count(where);

        return new StaticPagedList<T>(results, page.PageNumber, page.PageSize, total);
    }

ব্যাখ্যা

System.Linq.Dynamic আমাদের অর্ডারবাই পদ্ধতিতে স্ট্রিংয়ের মান সেট করতে দেয়। তবে এই এক্সটেনশনের অভ্যন্তরে স্ট্রিংটি ল্যাম্বডায় পার্স করা হবে। সুতরাং আমি ভেবেছিলাম যে যদি আমরা ল্যাম্বডাটিকে স্ট্রিংয়ে পার্স করে অর্ডারবাই পদ্ধতিতে দেব তবে এটি কার্যকর হবে। এবং এটি কাজ করে!


6
   private Func<T, object> GetOrderByExpression<T>(string sortColumn)
    {
        Func<T, object> orderByExpr = null;
        if (!String.IsNullOrEmpty(sortColumn))
        {
            Type sponsorResultType = typeof(T);

            if (sponsorResultType.GetProperties().Any(prop => prop.Name == sortColumn))
            {
                System.Reflection.PropertyInfo pinfo = sponsorResultType.GetProperty(sortColumn);
                orderByExpr = (data => pinfo.GetValue(data, null));
            }
        }
        return orderByExpr;
    }

    public List<T> OrderByDir<T>(IEnumerable<T> source, string dir, Func<T, object> OrderByColumn)
    {
        return dir.ToUpper() == "ASC" ? source.OrderBy(OrderByColumn).ToList() : source.OrderByDescending(OrderByColumn).ToList();``
    }

 // Call the code like below
        var orderByExpression= GetOrderByExpression<SearchResultsType>(sort);

    var data = OrderByDir<SponsorSearchResults>(resultRecords, SortDirectionString, orderByExpression);    

উজ্জ্বল! ঠিক আমার যা প্রয়োজন ছিল।
ব্র্যান্ডন গ্রিফিন

5

শর্তাধীন অবতীর্ণতার মোকাবিলার জন্য আমি এমন কিছু এখানে এনেছি। আপনি এটিকে keySelectorগতিশীলভাবে ছত্রাক উত্পন্ন করার অন্যান্য পদ্ধতির সাথে একত্রিত করতে পারেন ।

    public static IOrderedQueryable<TSource> OrderBy<TSource, TKey>(this IQueryable<TSource> source,
            System.Linq.Expressions.Expression<Func<TSource, TKey>> keySelector,
            System.ComponentModel.ListSortDirection sortOrder
            )
    {
        if (sortOrder == System.ComponentModel.ListSortDirection.Ascending)
            return source.OrderBy(keySelector);
        else
            return source.OrderByDescending(keySelector);
    }

ব্যবহার:

//imagine this is some parameter
var direction = System.ComponentModel.ListSortDirection.Ascending;
query = query.OrderBy(ec => ec.MyColumnName, direction);

লক্ষ্য করুন এটি আপনাকে .OrderByকোনও আইকুয়েরেবলের উপর নতুন প্যারামিটার দিয়ে এই এক্সটেনশানটিকে শৃঙ্খলাবদ্ধ করতে দেয় ।

// perhaps passed in as a request of user to change sort order
// var direction = System.ComponentModel.ListSortDirection.Ascending;
query = context.Orders
        .Where(o => o.Status == OrderStatus.Paid)
        .OrderBy(ec => ec.OrderPaidUtc, direction);

3

stringআপনি আপনার প্রশ্নে যেমন জিজ্ঞাসা করেছেন এটি আপনাকে পাস করতে দেয় না , তবে এটি এখনও আপনার পক্ষে কার্যকর হতে পারে।

OrderByDescendingপদ্ধতি লাগে Func<TSource, TKey>, যাতে আপনি আপনার ফাংশন এই ভাবে পুনর্লিখন করতে পারেন:

List<Student> QueryStudents<TKey>(Func<Student, TKey> orderBy)
{
    return existingStudents.OrderByDescending(orderBy).ToList();
}

OrderByDescendingপাশাপাশি অন্যান্য ওভারলোডগুলিও লাগে যা একটি Expression<Func<TSource, TKey>>, এবং / অথবা এ নেয় IComparer<TKey>। আপনি সেগুলি সন্ধান করতে পারেন এবং দেখতে পান যে তারা আপনাকে ব্যবহারের কিছু সরবরাহ করে কিনা।


এটি কাজ করে না কারণ আপনি টি-কে-এর ধরণ নির্ধারণ করেন না। পরিবর্তে <TKey> রাখতে আপনাকে আপনার <T> পরিবর্তন করতে হবে।
প্যাট্রিক দেশজার্ডিনস

এটিই আমার পক্ষে কাজ করেছিল! আমি এমন একটি ফাংশন চেয়েছিলাম যা উত্তীর্ণ বাউল মানের উপর নির্ভর করে একটি তালিকা আরোহণ, বা অবতরণকে অর্ডার করবে। আপনার কোডটি কিছুটা টুইট করে দুর্দান্ত কাজ করেছে!
জো গেইটি

ক্রিয়াকলাপে লিনকিউ: আইনেম্যারেবল <বুক> কাস্টমসোর্ট <টিকি> (ফানক <বুক, টিকি> নির্বাচনকারী, বুলিয়ান আরোহী) {অনুমানযোগ্য <বুক> বই = নমুনা ডেটা.বুকস; আরোহী ফিরে? Books.OrderBy (নির্বাচক): বই.অর্ডারবাইডেসেন্ডিং (নির্বাচক); }
লেসেক পি

1

আমার জন্য কাজ করা একমাত্র সমাধানটি এখানে নিও জিএনএভা দ্বারা https://gist.github.com/neoGeneva/1878868 পোস্ট করা হয়েছিল ।

আমি তার কোডটি পুনরায় পোস্ট করব কারণ এটি কার্যকরভাবে কাজ করে এবং আমি চাই না যে এটি আন্তঃবিশ্বগুলিতে হারিয়ে যেতে পারে!

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string sortExpression)
    {
        if (source == null)
            throw new ArgumentNullException("source", "source is null.");

        if (string.IsNullOrEmpty(sortExpression))
            throw new ArgumentException("sortExpression is null or empty.", "sortExpression");

        var parts = sortExpression.Split(' ');
        var isDescending = false;
        var propertyName = "";
        var tType = typeof(T);

        if (parts.Length > 0 && parts[0] != "")
        {
            propertyName = parts[0];

            if (parts.Length > 1)
            {
                isDescending = parts[1].ToLower().Contains("esc");
            }

            PropertyInfo prop = tType.GetProperty(propertyName);

            if (prop == null)
            {
                throw new ArgumentException(string.Format("No property '{0}' on type '{1}'", propertyName, tType.Name));
            }

            var funcType = typeof(Func<,>)
                .MakeGenericType(tType, prop.PropertyType);

            var lambdaBuilder = typeof(Expression)
                .GetMethods()
                .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2)
                .MakeGenericMethod(funcType);

            var parameter = Expression.Parameter(tType);
            var propExpress = Expression.Property(parameter, prop);

            var sortLambda = lambdaBuilder
                .Invoke(null, new object[] { propExpress, new ParameterExpression[] { parameter } });

            var sorter = typeof(Queryable)
                .GetMethods()
                .FirstOrDefault(x => x.Name == (isDescending ? "OrderByDescending" : "OrderBy") && x.GetParameters().Length == 2)
                .MakeGenericMethod(new[] { tType, prop.PropertyType });

            return (IQueryable<T>)sorter
                .Invoke(null, new object[] { source, sortLambda });
        }

        return source;
    }

1
  • আপনার কোডে ন্যুগেট প্যাকেজ ডায়নামাইট যুক্ত করুন

  • নাম স্থান ডায়নামাইট যুক্ত করুন x এক্সটেনশনগুলি উদাহরণস্বরূপ: ডায়নামাইট ব্যবহার করে; এক্সটেনশনগুলি;

  • যে কোনও এসকিউএল কোয়েরির মতো প্রশ্নের মাধ্যমে অর্ডার দিন উদাহরণস্বরূপ: শিক্ষার্থী Oআর্ডারবি ("সিটি ডিইএসসি, ঠিকানা") To টোললিস্ট ();


1

@ ইকারাসের প্রতিক্রিয়া বাড়ানোর জন্য: আপনি যদি দুটি ক্ষেত্র অনুসারে বাছাই করতে চান তবে আমি নীচের ফাংশনটি সম্পাদন করতে পারতাম (একটি ক্ষেত্রের জন্য Icarius এর প্রতিক্রিয়া খুব ভালভাবে কাজ করে)।

public static IQueryable<T> OrderByDynamic<T>(this IQueryable<T> q, string SortField1, string SortField2, bool Ascending)
        {
            var param = Expression.Parameter(typeof(T), "p");
            var body = GetBodyExp(SortField1, SortField2, param);
            var exp = Expression.Lambda(body, param);

            string method = Ascending ? "OrderBy" : "OrderByDescending";
            Type[] types = new Type[] { q.ElementType, exp.Body.Type };
            var mce = Expression.Call(typeof(Queryable), method, types, q.Expression, exp);
            return q.Provider.CreateQuery<T>(mce);
        }

এটি ল্যাম্বডা এক্সপ্রেশনটির জন্য দেহটি ফাংশনটি ফিরিয়ে দেয় এটি স্ট্রিং এবং ইন্টের সাথে কাজ করে তবে প্রতিটি প্রোগ্রামারের প্রয়োজন অনুসারে এটি কাজ করতে আরও প্রকার যুক্ত করার জন্য এটি যথেষ্ট is

public static NewExpression GetBodyExp(string field1, string field2, ParameterExpression Parametro)
        {    
            // SE OBTIENE LOS NOMBRES DE LOS TIPOS DE VARIABLE 
            string TypeName1 = Expression.Property(Parametro, field1).Type.Name;
            string TypeName2 = Expression.Property(Parametro, field2).Type.Name;

            // SE DECLARA EL TIPO ANONIMO SEGUN LOS TIPOS DE VARIABLES
            Type TypeAnonymous = null;
            if (TypeName1 == "String")
            {
                string var1 = "0";
                if (TypeName2 == "Int32")
                {
                    int var2 = 0;
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }    

            if (TypeName1 == "Int32")
            {
                int var1 = 0;
                if (TypeName2 == "Int32")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }

                if (TypeName2 == "String")
                {
                    string var2 = "0";
                    var example = new { var1, var2 };
                    TypeAnonymous = example.GetType();
                }    
            }

            //se declaran los TIPOS NECESARIOS PARA GENERAR EL BODY DE LA EXPRESION LAMBDA
            MemberExpression[] args = new[] { Expression.PropertyOrField(Parametro, field1), Expression.PropertyOrField(Parametro, field2) };
            ConstructorInfo CInfo = TypeAnonymous.GetConstructors()[0];
            IEnumerable<MemberInfo> a = TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property);

            //BODY 
            NewExpression body = Expression.New(CInfo, args, TypeAnonymous.GetMembers().Where(m => m.MemberType == MemberTypes.Property));

            return body;
        }

এটি ব্যবহার করার জন্য নিম্নলিখিতটি সম্পন্ন হয়েছে

IQueryable<MyClass> IqMyClass= context.MyClass.AsQueryable();
List<MyClass> ListMyClass= IqMyClass.OrderByDynamic("UserName", "IdMyClass", true).ToList();

এটি করার আরও ভাল উপায় যদি থাকে তবে তারা এটিকে ভাগ করে নিলে দুর্দান্ত হবে

আমি এটির জন্য ধন্যবাদ সমাধান করতে সক্ষম হয়েছি: আমি কীভাবে লিনকের সাথে একাধিক সম্পত্তি ল্যাম্বদা এক্সপ্রেশন করতে পারি


-2

আমি পার্টিতে দেরিতে আছি কিন্তু এই সমাধানগুলির কোনওোটাই আমার পক্ষে কার্যকর হয়নি। আমি সিস্টেম.লিনক.ডায়নামিক চেষ্টা করতে আগ্রহী ছিলাম, তবে আমি এটি নুগেটে খুঁজে পেলাম না, অবহেলিত হতে পারে? যেভাবেই হোক ...

এখানে আমি একটি সমাধান নিয়ে এসেছি। আমার ডায়নামিকভাবে অর্ডারবি , অর্ডারবাইডেসেন্ডিং এবং অর্ডারবাই> থটারবিয়ের মিশ্রণটি ব্যবহার করা দরকার

আমি কেবল আমার তালিকার অবজেক্টের জন্য একটি এক্সটেনশন পদ্ধতি তৈরি করেছি, যা আমি জানি কিছুটা হ্যাকি ... আমি যদি এটি কিছু করতাম তবে আমি এটির সুপারিশ করতাম না, তবে এটি বন্ধ করার পক্ষে ভাল।

List<Employee> Employees = GetAllEmployees();

foreach(Employee oEmployee in Employees.ApplyDynamicSort(eEmployeeSort))
{
    //do stuff
}

public static IOrderedEnumerable<Employee> ApplyDynamicSort(this List<Employee> lEmployees, Enums.EmployeeSort eEmployeeSort)
{
    switch (eEmployeeSort)
    {
        case Enums.EmployeeSort.Name_ASC:
            return lEmployees.OrderBy(x => x.Name);
        case Enums.EmployeeSort.Name_DESC:
            return lEmployees.OrderByDescending(x => x.Name);
        case Enums.EmployeeSort.Department_ASC_Salary_DESC:
            return lEmployees.OrderBy(x => x.Department).ThenByDescending(y => y.Salary);
        default:
            return lEmployees.OrderBy(x => x.Name);
    }
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.